fimo 0.9.0-experimental.1 → 0.9.0

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.
Files changed (205) hide show
  1. package/assets/skills/fimo/SKILL.md +17 -54
  2. package/assets/skills/fimo-cli/SKILL.md +15 -15
  3. package/assets/skills/fimo-cli/references/agent-setup.md +36 -53
  4. package/assets/skills/fimo-cli/references/agents.md +4 -4
  5. package/assets/skills/fimo-cli/references/content.md +2 -2
  6. package/assets/skills/fimo-cli/references/deploy.md +1 -1
  7. package/assets/skills/fimo-cli/references/forms.md +1 -1
  8. package/assets/skills/fimo-cli/references/init.md +16 -24
  9. package/assets/skills/fimo-cli/references/project-vars-and-secrets.md +14 -11
  10. package/assets/skills/fimo-cli/references/projects.md +1 -1
  11. package/assets/skills/fimo-cli/references/support.md +11 -9
  12. package/assets/skills/fimo-cli/references/webhooks.md +3 -1
  13. package/assets/skills/fimo-code/SKILL.md +67 -0
  14. package/assets/skills/fimo-migration/SKILL.md +2 -22
  15. package/assets/skills/fimo-migration/references/content.md +0 -24
  16. package/assets/skills/fimo-migration/references/manifest.md +0 -35
  17. package/assets/skills/fimo-migration/references/qa.md +2 -24
  18. package/assets/skills/fimo-migration/references/workflow.md +1 -12
  19. package/assets/skills/fimo-studio/references/visual-editing.md +3 -5
  20. package/assets/skills/fimo-studio/references/webhooks.md +1 -1
  21. package/dist/build/vite/plugins/translations.d.ts +8 -2
  22. package/dist/build/vite/plugins/translations.js +121 -3
  23. package/dist/cli/bundle.json +2 -2
  24. package/dist/cli/index.js +311 -1209
  25. package/dist/runtime/app/FimoProviders.d.ts +11 -1
  26. package/dist/runtime/app/FimoProviders.js +23 -2
  27. package/dist/runtime/content/client/createContentClient.js +2 -1
  28. package/dist/runtime/i18n/FimoLocaleContext.js +0 -1
  29. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +3 -5
  30. package/dist/runtime/primitives/lib/useFimoLiveValue.js +4 -11
  31. package/dist/runtime/primitives/translations.d.ts +27 -1
  32. package/dist/runtime/primitives/translations.js +85 -7
  33. package/dist/runtime/shared/fimo-config.server.js +0 -1
  34. package/dist/runtime/shared/fimo-config.types.d.ts +0 -2
  35. package/dist/scripts/extract-translations.js +11 -16
  36. package/dist/scripts/lib/validation-report.d.ts +0 -1
  37. package/dist/scripts/lib/validation-report.js +0 -3
  38. package/dist/scripts/validate-route-metadata.js +0 -11
  39. package/package.json +4 -84
  40. package/release.json +2 -2
  41. package/scripts/postinstall.mjs +11 -65
  42. package/templates/react-router/AGENTS.md +44 -0
  43. package/templates/react-router/CLAUDE.md +5 -0
  44. package/templates/react-router/package.json +1 -1
  45. package/assets/agents-starter/AGENTS.md +0 -66
  46. package/assets/skills/fimo-migration/references/wrap.md +0 -617
  47. package/dist/runtime/astro/index.d.ts +0 -2
  48. package/dist/runtime/astro/index.js +0 -3
  49. package/dist/runtime/astro/server.d.ts +0 -3
  50. package/dist/runtime/astro/server.js +0 -6
  51. package/dist/runtime/next/FimoNextProviders.d.ts +0 -24
  52. package/dist/runtime/next/FimoNextProviders.js +0 -20
  53. package/dist/runtime/next/FimoNextScripts.d.ts +0 -10
  54. package/dist/runtime/next/FimoNextScripts.js +0 -19
  55. package/dist/runtime/next/index.d.ts +0 -14
  56. package/dist/runtime/next/index.js +0 -14
  57. package/dist/runtime/next/server.d.ts +0 -2
  58. package/dist/runtime/next/server.js +0 -4
  59. package/dist/runtime/nuxt/composables.d.ts +0 -9
  60. package/dist/runtime/nuxt/composables.js +0 -57
  61. package/dist/runtime/nuxt/index.d.ts +0 -3
  62. package/dist/runtime/nuxt/index.js +0 -6
  63. package/dist/runtime/nuxt/server.d.ts +0 -3
  64. package/dist/runtime/nuxt/server.js +0 -6
  65. package/dist/runtime/primitives/labels-hook.d.ts +0 -33
  66. package/dist/runtime/primitives/labels-hook.js +0 -41
  67. package/dist/runtime/primitives/labels-store.d.ts +0 -12
  68. package/dist/runtime/primitives/labels-store.js +0 -60
  69. package/dist/runtime/react/FimoSpaProviders.d.ts +0 -36
  70. package/dist/runtime/react/FimoSpaProviders.js +0 -63
  71. package/dist/runtime/react/index.d.ts +0 -33
  72. package/dist/runtime/react/index.js +0 -39
  73. package/dist/runtime/remix/FimoRemixProviders.d.ts +0 -16
  74. package/dist/runtime/remix/FimoRemixProviders.js +0 -12
  75. package/dist/runtime/remix/FimoRemixScripts.d.ts +0 -10
  76. package/dist/runtime/remix/FimoRemixScripts.js +0 -19
  77. package/dist/runtime/remix/index.d.ts +0 -12
  78. package/dist/runtime/remix/index.js +0 -11
  79. package/dist/runtime/remix/server.d.ts +0 -2
  80. package/dist/runtime/remix/server.js +0 -2
  81. package/dist/runtime/shared/display-routes.d.ts +0 -14
  82. package/dist/runtime/shared/display-routes.js +0 -25
  83. package/dist/runtime/shared/i18n-messages.d.ts +0 -29
  84. package/dist/runtime/shared/i18n-messages.js +0 -79
  85. package/dist/runtime/shared/labels-fetch.pure.d.ts +0 -17
  86. package/dist/runtime/shared/labels-fetch.pure.js +0 -71
  87. package/dist/runtime/shared/labels-fetch.server.d.ts +0 -7
  88. package/dist/runtime/shared/labels-fetch.server.js +0 -97
  89. package/dist/runtime/shared/labels-t.pure.d.ts +0 -2
  90. package/dist/runtime/shared/labels-t.pure.js +0 -10
  91. package/dist/runtime/strapi/client.d.ts +0 -30
  92. package/dist/runtime/strapi/client.js +0 -89
  93. package/dist/runtime/strapi/index.d.ts +0 -5
  94. package/dist/runtime/strapi/index.js +0 -6
  95. package/dist/runtime/strapi/reshape.d.ts +0 -46
  96. package/dist/runtime/strapi/reshape.js +0 -114
  97. package/dist/runtime/svelte/index.d.ts +0 -3
  98. package/dist/runtime/svelte/index.js +0 -7
  99. package/dist/runtime/svelte/server.d.ts +0 -3
  100. package/dist/runtime/svelte/server.js +0 -6
  101. package/dist/runtime/vue/index.d.ts +0 -3
  102. package/dist/runtime/vue/index.js +0 -5
  103. package/dist/runtime/vue/server.d.ts +0 -3
  104. package/dist/runtime/vue/server.js +0 -6
  105. package/dist/runtime/wrapped/FimoFileLink.d.ts +0 -17
  106. package/dist/runtime/wrapped/FimoFileLink.js +0 -19
  107. package/dist/runtime/wrapped/FimoMediaImage.d.ts +0 -14
  108. package/dist/runtime/wrapped/FimoMediaImage.js +0 -20
  109. package/dist/runtime/wrapped/FimoMediaVideo.d.ts +0 -14
  110. package/dist/runtime/wrapped/FimoMediaVideo.js +0 -20
  111. package/dist/runtime/wrapped/FimoRichTextBlock.d.ts +0 -23
  112. package/dist/runtime/wrapped/FimoRichTextBlock.js +0 -22
  113. package/dist/runtime/wrapped/FimoText.d.ts +0 -22
  114. package/dist/runtime/wrapped/FimoText.js +0 -20
  115. package/dist/runtime/wrapped/WrappedContentContext.d.ts +0 -28
  116. package/dist/runtime/wrapped/WrappedContentContext.js +0 -30
  117. package/dist/runtime/wrapped/WrappedProviders.d.ts +0 -33
  118. package/dist/runtime/wrapped/WrappedProviders.js +0 -101
  119. package/dist/runtime/wrapped/dom.d.ts +0 -4
  120. package/dist/runtime/wrapped/dom.js +0 -23
  121. package/dist/runtime/wrapped/server.d.ts +0 -77
  122. package/dist/runtime/wrapped/server.js +0 -237
  123. package/dist/scripts/apply-wrap-templates.d.ts +0 -61
  124. package/dist/scripts/apply-wrap-templates.js +0 -132
  125. package/dist/scripts/apply-wrap.d.ts +0 -46
  126. package/dist/scripts/apply-wrap.js +0 -278
  127. package/dist/scripts/cms/discover.d.ts +0 -2
  128. package/dist/scripts/cms/discover.js +0 -17
  129. package/dist/scripts/cms/example-content.d.ts +0 -33
  130. package/dist/scripts/cms/example-content.js +0 -162
  131. package/dist/scripts/cms/strapi/detect.d.ts +0 -2
  132. package/dist/scripts/cms/strapi/detect.js +0 -186
  133. package/dist/scripts/cms/strapi/index.d.ts +0 -3
  134. package/dist/scripts/cms/strapi/index.js +0 -25
  135. package/dist/scripts/cms/strapi/list-source.d.ts +0 -17
  136. package/dist/scripts/cms/strapi/list-source.js +0 -90
  137. package/dist/scripts/cms/strapi/schema-map.d.ts +0 -9
  138. package/dist/scripts/cms/strapi/schema-map.js +0 -277
  139. package/dist/scripts/cms/strapi/shapes.d.ts +0 -23
  140. package/dist/scripts/cms/strapi/shapes.js +0 -38
  141. package/dist/scripts/cms/strapi/touchpoints.d.ts +0 -31
  142. package/dist/scripts/cms/strapi/touchpoints.js +0 -145
  143. package/dist/scripts/cms/strapi/wire-client.d.ts +0 -15
  144. package/dist/scripts/cms/strapi/wire-client.js +0 -107
  145. package/dist/scripts/cms/strapi/wiring.d.ts +0 -17
  146. package/dist/scripts/cms/strapi/wiring.js +0 -46
  147. package/dist/scripts/cms/types.d.ts +0 -145
  148. package/dist/scripts/cms/types.js +0 -11
  149. package/dist/scripts/detect-framework.d.ts +0 -39
  150. package/dist/scripts/detect-framework.js +0 -133
  151. package/dist/scripts/draft-rows.d.ts +0 -19
  152. package/dist/scripts/draft-rows.js +0 -135
  153. package/dist/scripts/draft-schemas.d.ts +0 -27
  154. package/dist/scripts/draft-schemas.js +0 -188
  155. package/dist/scripts/frameworks/astro/routes.d.ts +0 -3
  156. package/dist/scripts/frameworks/astro/routes.js +0 -55
  157. package/dist/scripts/frameworks/astro/template-style.d.ts +0 -7
  158. package/dist/scripts/frameworks/astro/template-style.js +0 -19
  159. package/dist/scripts/frameworks/discover.d.ts +0 -70
  160. package/dist/scripts/frameworks/discover.js +0 -178
  161. package/dist/scripts/frameworks/next/rewire.d.ts +0 -30
  162. package/dist/scripts/frameworks/next/rewire.js +0 -258
  163. package/dist/scripts/frameworks/next/routes.d.ts +0 -17
  164. package/dist/scripts/frameworks/next/routes.js +0 -134
  165. package/dist/scripts/frameworks/next/wiring.d.ts +0 -33
  166. package/dist/scripts/frameworks/next/wiring.js +0 -186
  167. package/dist/scripts/frameworks/nuxt/routes.d.ts +0 -3
  168. package/dist/scripts/frameworks/nuxt/routes.js +0 -54
  169. package/dist/scripts/frameworks/nuxt/template-style.d.ts +0 -9
  170. package/dist/scripts/frameworks/nuxt/template-style.js +0 -27
  171. package/dist/scripts/frameworks/remix/routes.d.ts +0 -3
  172. package/dist/scripts/frameworks/remix/routes.js +0 -50
  173. package/dist/scripts/frameworks/support.d.ts +0 -75
  174. package/dist/scripts/frameworks/support.js +0 -165
  175. package/dist/scripts/frameworks/sveltekit/routes.d.ts +0 -3
  176. package/dist/scripts/frameworks/sveltekit/routes.js +0 -50
  177. package/dist/scripts/frameworks/sveltekit/template-style.d.ts +0 -9
  178. package/dist/scripts/frameworks/sveltekit/template-style.js +0 -54
  179. package/dist/scripts/frameworks/vite/routes.d.ts +0 -15
  180. package/dist/scripts/frameworks/vite/routes.js +0 -93
  181. package/dist/scripts/frameworks/vite/wiring.d.ts +0 -10
  182. package/dist/scripts/frameworks/vite/wiring.js +0 -99
  183. package/dist/scripts/i18n-namespace.d.ts +0 -27
  184. package/dist/scripts/i18n-namespace.js +0 -155
  185. package/dist/scripts/render-parity/capture.d.ts +0 -56
  186. package/dist/scripts/render-parity/capture.js +0 -366
  187. package/dist/scripts/render-parity/compare.d.ts +0 -59
  188. package/dist/scripts/render-parity/compare.js +0 -0
  189. package/dist/scripts/render-parity/normalize-html.d.ts +0 -13
  190. package/dist/scripts/render-parity/normalize-html.js +0 -223
  191. package/dist/scripts/render-parity/types.d.ts +0 -41
  192. package/dist/scripts/render-parity/types.js +0 -1
  193. package/dist/scripts/scan-wrappable.d.ts +0 -196
  194. package/dist/scripts/scan-wrappable.js +0 -1232
  195. /package/assets/skills/{fimo → fimo-code}/assets/react-router-locales/navigation.tsx +0 -0
  196. /package/assets/skills/{fimo → fimo-code}/assets/react-router-locales/routes.ts +0 -0
  197. /package/assets/skills/{fimo → fimo-code}/references/assets.md +0 -0
  198. /package/assets/skills/{fimo → fimo-code}/references/content.md +0 -0
  199. /package/assets/skills/{fimo → fimo-code}/references/design.md +0 -0
  200. /package/assets/skills/{fimo → fimo-code}/references/forms.md +0 -0
  201. /package/assets/skills/{fimo → fimo-code}/references/labels.md +0 -0
  202. /package/assets/skills/{fimo → fimo-code}/references/locales.md +0 -0
  203. /package/assets/skills/{fimo → fimo-code}/references/setup-plain-vite.md +0 -0
  204. /package/assets/skills/{fimo → fimo-code}/references/setup-react-router.md +0 -0
  205. /package/assets/skills/{fimo → fimo-code}/references/ui.md +0 -0
@@ -1,3 +0,0 @@
1
- import type { CmsConnector } from '../types.js';
2
- /** Assemble the Strapi connector for a wrapped frontend, or null if no Strapi. */
3
- export declare function strapiConnector(frontendDir: string): CmsConnector | null;
@@ -1,25 +0,0 @@
1
- import { detectStrapi } from './detect.js';
2
- import { renderStrapiListSource } from './list-source.js';
3
- import { readStrapiSchemas } from './schema-map.js';
4
- import { detectStrapiTouchpoints } from './touchpoints.js';
5
- import { wireStrapiClient } from './wire-client.js';
6
- /** Assemble the Strapi connector for a wrapped frontend, or null if no Strapi. */
7
- export function strapiConnector(frontendDir) {
8
- const detection = detectStrapi(frontendDir);
9
- if (!detection) {
10
- return null;
11
- }
12
- return {
13
- detection,
14
- readSchemas: () => (detection.appDir ? readStrapiSchemas(detection.appDir) : []),
15
- renderListSource: (schemas, serverImport) => schemas.length > 0 ? renderStrapiListSource(schemas, serverImport) : null,
16
- wireClient: (cwd, schemas) => detection.clientDir
17
- ? wireStrapiClient(cwd, detection.clientDir, schemas)
18
- : {
19
- changes: [],
20
- warnings: ['No Strapi client directory detected — wire fimoStrapiFetch manually.'],
21
- wired: false,
22
- },
23
- detectTouchpoints: (cwd) => detectStrapiTouchpoints(cwd),
24
- };
25
- }
@@ -1,17 +0,0 @@
1
- import type { CmsCollectionSchema } from '../types.js';
2
- /**
3
- * Emit a GENERIC Strapi-list read helper as source text. The scaffold pushes a
4
- * CMS model + example content into Fimo; this helper lets a site's LIST views
5
- * render that example content from Fimo (detail views + dynamic zones stay on
6
- * the CMS). It is intentionally best-effort and site-neutral — the agent drops
7
- * it in and wires the site's list fetchers to it (the non-deterministic last
8
- * mile the machine can't own).
9
- *
10
- * The reshape is grounded in the Fimo runtime contract (`fetchEntries` →
11
- * `{ id, data }`, media fields `{ url, ... }`, richtext fields a ProseMirror
12
- * doc): richtext docs are flattened to plain text (so list helpers like a
13
- * read-time estimate that expect a string don't choke), media and scalars pass
14
- * through, and an empty Fimo result returns null so the caller falls back to
15
- * the live CMS.
16
- */
17
- export declare function renderStrapiListSource(schemas: CmsCollectionSchema[], serverImport: string): string;
@@ -1,90 +0,0 @@
1
- /**
2
- * Emit a GENERIC Strapi-list read helper as source text. The scaffold pushes a
3
- * CMS model + example content into Fimo; this helper lets a site's LIST views
4
- * render that example content from Fimo (detail views + dynamic zones stay on
5
- * the CMS). It is intentionally best-effort and site-neutral — the agent drops
6
- * it in and wires the site's list fetchers to it (the non-deterministic last
7
- * mile the machine can't own).
8
- *
9
- * The reshape is grounded in the Fimo runtime contract (`fetchEntries` →
10
- * `{ id, data }`, media fields `{ url, ... }`, richtext fields a ProseMirror
11
- * doc): richtext docs are flattened to plain text (so list helpers like a
12
- * read-time estimate that expect a string don't choke), media and scalars pass
13
- * through, and an empty Fimo result returns null so the caller falls back to
14
- * the live CMS.
15
- */
16
- export function renderStrapiListSource(schemas, serverImport) {
17
- const entries = schemas
18
- .filter((schema) => !schema.isSingleton)
19
- .map((schema) => ` '${schema.sourceUid}': '${schema.name}',`)
20
- .join('\n');
21
- return `// AUTO-GENERATED by \`fimo migrate --scaffold-cms\`. Renders the CMS collections
22
- // scaffolded into Fimo (EXAMPLE content) for LIST views, so pages show the fake
23
- // content from Fimo while detail views and dynamic zones stay on your CMS.
24
- //
25
- // Wire your CMS client's list methods to this (see the migration prompt), e.g.
26
- // inside fetchMany/fetchAll: const fimo = await fimoCmsList(uid, { limit });
27
- // if (fimo) return fimo;
28
- // Best-effort and generic by design — adjust per component as needed.
29
- import { fetchEntries } from '${serverImport}';
30
-
31
- // Strapi content-type UID -> Fimo type name (from the scaffold).
32
- const FIMO_TYPES: Record<string, string> = {
33
- ${entries}
34
- };
35
-
36
- function docToText(node: unknown): string {
37
- if (!node || typeof node !== 'object') {
38
- return '';
39
- }
40
- const record = node as { text?: unknown; content?: unknown };
41
- if (typeof record.text === 'string') {
42
- return record.text;
43
- }
44
- if (Array.isArray(record.content)) {
45
- return record.content.map(docToText).join(' ');
46
- }
47
- return '';
48
- }
49
-
50
- function reshapeValue(value: unknown): unknown {
51
- // Richtext is a ProseMirror doc -> flatten to plain text so string consumers
52
- // (excerpts, read-time) are safe. Media { url, ... } and scalars pass through.
53
- if (value && typeof value === 'object' && (value as { type?: unknown }).type === 'doc') {
54
- return docToText(value);
55
- }
56
- return value;
57
- }
58
-
59
- function reshape(data: Record<string, unknown>): Record<string, unknown> {
60
- const out: Record<string, unknown> = {};
61
- for (const [key, value] of Object.entries(data)) {
62
- out[key] = reshapeValue(value);
63
- }
64
- return out;
65
- }
66
-
67
- /**
68
- * A Strapi-v5-shaped list envelope built from the scaffolded Fimo content, or
69
- * null when nothing was scaffolded for this UID (fall back to the live CMS).
70
- */
71
- export async function fimoCmsList(
72
- uid: string,
73
- options: { limit?: number } = {}
74
- ): Promise<{ data: Record<string, unknown>[]; meta: { pagination: Record<string, number> } } | null> {
75
- const typeName = FIMO_TYPES[uid];
76
- if (!typeName) {
77
- return null;
78
- }
79
- const entries = await fetchEntries(typeName, options.limit ? { limit: options.limit } : {});
80
- if (entries.length === 0) {
81
- return null;
82
- }
83
- const data = entries.map((entry) => ({ id: entry.id, documentId: entry.id, ...reshape(entry.data) }));
84
- return {
85
- data,
86
- meta: { pagination: { page: 1, pageSize: data.length, pageCount: 1, total: data.length } },
87
- };
88
- }
89
- `;
90
- }
@@ -1,9 +0,0 @@
1
- import type { CmsCollectionSchema } from '../types.js';
2
- /** `blog-post` / `blog_posts` / `Blog Post` → `BlogPost`. */
3
- export declare function pascalCase(name: string): string;
4
- /**
5
- * Read every local Strapi content-type schema into the Fimo schema shape.
6
- * Two passes: first name every type (so relation targets resolve to Fimo
7
- * names), then map fields (resolving components into editable blocks).
8
- */
9
- export declare function readStrapiSchemas(strapiAppDir: string): CmsCollectionSchema[];
@@ -1,277 +0,0 @@
1
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- /**
4
- * Strapi content-type schema → Fimo schema mapping. Strapi keeps every content
5
- * type as `apps/strapi/src/api/<api>/content-types/<ct>/schema.json` and every
6
- * component as `apps/strapi/src/components/<category>/<name>.json` — local,
7
- * declarative sources of truth — so the Fimo schemas can be derived without ever
8
- * contacting the CMS. All Strapi specifics live here (and in `detect.ts`).
9
- *
10
- * Structural content maps to Fimo **blocks** (Studio-editable ordered content):
11
- * a dynamic zone → a `oneOf` union of one variant per allowed component; a
12
- * single or repeatable component → a fixed `{ fields }` block. Each dynamic-zone
13
- * variant carries the original Strapi component id as a hidden, defaulted
14
- * `__component` field, so the served block is losslessly reversible to the
15
- * frontend's `block.__component` shape without a runtime lookup.
16
- */
17
- // Mirrors MAX_BLOCK_VARIANTS in @fimo/api-contracts — a `oneOf` beyond this
18
- // fails schema validation, so we cap and report rather than push an invalid set.
19
- const MAX_BLOCK_VARIANTS = 40;
20
- // Bound recursion into nested components; beyond this a nested component falls
21
- // back to `json` (still round-trips, just not granularly editable that deep).
22
- const MAX_COMPONENT_DEPTH = 5;
23
- // Field-name + type-name contracts from @fimo/api-contracts (content-fields.ts).
24
- const FIELD_NAME_PATTERN = /^[A-Za-z_]\w*$/;
25
- const RESERVED_BLOCK_FIELD_NAMES = new Set(['_key', '_type']);
26
- const COMPONENT_DISCRIMINATOR = '__component';
27
- const TO_MANY = new Set(['oneToMany', 'manyToMany']);
28
- /** `blog-post` / `blog_posts` / `Blog Post` → `BlogPost`. */
29
- export function pascalCase(name) {
30
- return name
31
- .replace(/[^a-zA-Z0-9]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
32
- .replace(/^(.)/, (c) => c.toUpperCase());
33
- }
34
- /** Strapi component uid (`sections.hero`) → a valid Fimo block variant uid
35
- * (`SectionsHero`, matching `CONTENT_TYPE_NAME_PATTERN` /^[A-Za-z]\w*$/). */
36
- function variantUid(componentUid) {
37
- const pascal = pascalCase(componentUid);
38
- return /^[A-Za-z]/.test(pascal) ? pascal : `Block${pascal}`;
39
- }
40
- /**
41
- * Map one Strapi attribute to a Fimo field. Returns `null` for fields Fimo does
42
- * not model as content (the slug uid, passwords). Relations to imported types
43
- * become references; components and dynamic zones become blocks; relations to
44
- * non-imported targets (users, plugins) fall back to `json`.
45
- */
46
- function mapAttribute(field, attr, ctx) {
47
- if (field === 'slug') {
48
- // Fimo reserves `slug` as entry metadata (set inside the row data).
49
- return null;
50
- }
51
- switch (attr.type) {
52
- case 'password':
53
- return null;
54
- case 'string':
55
- case 'text':
56
- case 'email':
57
- case 'uid':
58
- case 'enumeration':
59
- return { type: 'string' };
60
- case 'richtext':
61
- case 'blocks':
62
- return { type: 'richtext' };
63
- case 'integer':
64
- case 'biginteger':
65
- case 'decimal':
66
- case 'float':
67
- return { type: 'number' };
68
- case 'boolean':
69
- return { type: 'boolean' };
70
- case 'date':
71
- case 'datetime':
72
- case 'time':
73
- case 'timestamp':
74
- return { type: 'date' };
75
- case 'media':
76
- return { type: 'media' };
77
- case 'component':
78
- return mapComponentAttribute(attr, ctx);
79
- case 'dynamiczone':
80
- return mapDynamicZone(attr, ctx);
81
- case 'json':
82
- return { type: 'json' };
83
- case 'relation': {
84
- const targetName = attr.target ? ctx.uidToName.get(attr.target) : undefined;
85
- if (!targetName) {
86
- // Relation to a non-imported target (users-permissions, other plugins).
87
- return { type: 'json' };
88
- }
89
- const many = attr.relation ? TO_MANY.has(attr.relation) : false;
90
- return many ? { type: 'reference', target: targetName, many: true } : { type: 'reference', target: targetName };
91
- }
92
- default:
93
- // Unknown/custom attribute — never crash; keep the value verbatim.
94
- return { type: 'json' };
95
- }
96
- }
97
- /** Recursively map a component's attributes to a Fimo block-fields record.
98
- * Returns null on a missing component, a cycle, or past the depth cap. */
99
- function componentFields(componentUid, ctx) {
100
- if (!componentUid || ctx.depth > MAX_COMPONENT_DEPTH || ctx.visiting.has(componentUid)) {
101
- return null;
102
- }
103
- const component = ctx.components.get(componentUid);
104
- if (!component) {
105
- return null;
106
- }
107
- const childCtx = {
108
- ...ctx,
109
- depth: ctx.depth + 1,
110
- visiting: new Set(ctx.visiting).add(componentUid),
111
- };
112
- const fields = {};
113
- for (const [name, attr] of Object.entries(component.attributes ?? {})) {
114
- if (!FIELD_NAME_PATTERN.test(name) || RESERVED_BLOCK_FIELD_NAMES.has(name) || name === COMPONENT_DISCRIMINATOR) {
115
- continue;
116
- }
117
- const mapped = mapAttribute(name, attr, childCtx);
118
- if (mapped) {
119
- fields[name] = mapped;
120
- }
121
- }
122
- return fields;
123
- }
124
- /** A single/repeatable Strapi component → a fixed `{ fields }` blocks field.
125
- * Single (non-repeatable) is capped to one item; repeatable is unbounded. A
126
- * component that can't resolve (missing/cyclic/too deep) falls back to json. */
127
- function mapComponentAttribute(attr, ctx) {
128
- const fields = componentFields(attr.component, ctx);
129
- if (!fields) {
130
- return { type: 'json' };
131
- }
132
- const schema = { fields };
133
- return attr.repeatable ? { type: 'blocks', schema } : { type: 'blocks', schema, max: 1 };
134
- }
135
- /** A Strapi dynamic zone → a `oneOf` blocks field, one variant per allowed
136
- * component. Each variant bakes a hidden, defaulted `__component` so the stored
137
- * block reverses losslessly to the frontend's `block.__component` shape. */
138
- function mapDynamicZone(attr, ctx) {
139
- const componentUids = attr.components ?? [];
140
- if (componentUids.length > MAX_BLOCK_VARIANTS) {
141
- console.warn(`[migration] dynamic zone has ${componentUids.length} components; Fimo blocks allow at most ` +
142
- `${MAX_BLOCK_VARIANTS} variants — mapping the first ${MAX_BLOCK_VARIANTS}, dropping ` +
143
- `${componentUids.length - MAX_BLOCK_VARIANTS}.`);
144
- }
145
- const seenUids = new Set();
146
- const variants = [];
147
- for (const componentUid of componentUids.slice(0, MAX_BLOCK_VARIANTS)) {
148
- const fields = componentFields(componentUid, ctx);
149
- if (!fields) {
150
- continue;
151
- }
152
- // Dedupe pascalCase collisions (`a.hero` + `a_hero` → `AHero`); the baked
153
- // `__component` still carries each variant's true source id.
154
- let uid = variantUid(componentUid);
155
- for (let n = 2; seenUids.has(uid); n += 1) {
156
- uid = `${variantUid(componentUid)}${n}`;
157
- }
158
- seenUids.add(uid);
159
- const component = ctx.components.get(componentUid);
160
- variants.push({
161
- uid,
162
- label: component?.info?.displayName ?? uid,
163
- fields: {
164
- [COMPONENT_DISCRIMINATOR]: { type: 'string', default: componentUid, editable: false },
165
- ...fields,
166
- },
167
- });
168
- }
169
- if (variants.length === 0) {
170
- return { type: 'json' };
171
- }
172
- return { type: 'blocks', schema: { oneOf: variants } };
173
- }
174
- function mapSchema(raw, uidToName, components) {
175
- const info = raw.schema.info ?? {};
176
- const name = uidToName.get(raw.uid);
177
- const fields = {};
178
- const ctx = { uidToName, components, visiting: new Set(), depth: 0 };
179
- for (const [field, attr] of Object.entries(raw.schema.attributes ?? {})) {
180
- const mapped = mapAttribute(field, attr, ctx);
181
- if (mapped) {
182
- fields[field] = mapped;
183
- }
184
- }
185
- return {
186
- name,
187
- label: info.displayName || name,
188
- isSingleton: raw.schema.kind === 'singleType',
189
- localized: raw.schema.pluginOptions?.i18n?.localized === true,
190
- fields,
191
- sourceUid: raw.uid,
192
- sourcePath: `/${info.pluralName ?? raw.contentTypeName}`,
193
- };
194
- }
195
- /** Walk `<strapiAppDir>/src/api/<api>/content-types/<ct>/schema.json`. */
196
- function collectRawSchemas(strapiAppDir) {
197
- const apiDir = join(strapiAppDir, 'src', 'api');
198
- if (!existsSync(apiDir)) {
199
- return [];
200
- }
201
- const raws = [];
202
- for (const apiEntry of readdirSync(apiDir, { withFileTypes: true })) {
203
- if (!apiEntry.isDirectory()) {
204
- continue;
205
- }
206
- const ctRoot = join(apiDir, apiEntry.name, 'content-types');
207
- if (!existsSync(ctRoot)) {
208
- continue;
209
- }
210
- for (const ctEntry of readdirSync(ctRoot, { withFileTypes: true })) {
211
- if (!ctEntry.isDirectory()) {
212
- continue;
213
- }
214
- const schemaPath = join(ctRoot, ctEntry.name, 'schema.json');
215
- if (!existsSync(schemaPath)) {
216
- continue;
217
- }
218
- try {
219
- const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
220
- raws.push({
221
- uid: `api::${apiEntry.name}.${ctEntry.name}`,
222
- apiName: apiEntry.name,
223
- contentTypeName: ctEntry.name,
224
- schema,
225
- });
226
- }
227
- catch {
228
- // Malformed schema.json — skip, never abort the whole import.
229
- }
230
- }
231
- }
232
- return raws;
233
- }
234
- /** Walk `<strapiAppDir>/src/components/<category>/<name>.json` into a uid→schema
235
- * map. Strapi component uids are `<category>.<name>` (the dir + filename). */
236
- function collectComponents(strapiAppDir) {
237
- const components = new Map();
238
- const componentsDir = join(strapiAppDir, 'src', 'components');
239
- if (!existsSync(componentsDir)) {
240
- return components;
241
- }
242
- for (const categoryEntry of readdirSync(componentsDir, { withFileTypes: true })) {
243
- if (!categoryEntry.isDirectory()) {
244
- continue;
245
- }
246
- const categoryDir = join(componentsDir, categoryEntry.name);
247
- for (const fileEntry of readdirSync(categoryDir, { withFileTypes: true })) {
248
- if (!fileEntry.isFile() || !fileEntry.name.endsWith('.json')) {
249
- continue;
250
- }
251
- const uid = `${categoryEntry.name}.${fileEntry.name.slice(0, -'.json'.length)}`;
252
- try {
253
- const parsed = JSON.parse(readFileSync(join(categoryDir, fileEntry.name), 'utf8'));
254
- components.set(uid, { ...parsed, uid });
255
- }
256
- catch {
257
- // Malformed component json — skip, never abort the whole import.
258
- }
259
- }
260
- }
261
- return components;
262
- }
263
- /**
264
- * Read every local Strapi content-type schema into the Fimo schema shape.
265
- * Two passes: first name every type (so relation targets resolve to Fimo
266
- * names), then map fields (resolving components into editable blocks).
267
- */
268
- export function readStrapiSchemas(strapiAppDir) {
269
- const raws = collectRawSchemas(strapiAppDir);
270
- const components = collectComponents(strapiAppDir);
271
- const uidToName = new Map();
272
- for (const raw of raws) {
273
- const singular = raw.schema.info?.singularName ?? raw.contentTypeName;
274
- uidToName.set(raw.uid, pascalCase(singular));
275
- }
276
- return raws.map((raw) => mapSchema(raw, uidToName, components)).sort((a, b) => a.name.localeCompare(b.name));
277
- }
@@ -1,23 +0,0 @@
1
- import type { CmsCollectionSchema } from '../types.js';
2
- /**
3
- * Field-shape manifest for the `fimo/strapi` reshape. A Fimo blocks value is
4
- * always an array, but a Strapi single component reads as an object — so the
5
- * reshape needs to know, per structural field, whether to unwrap the one-item
6
- * array (`single`) or keep it (`list`), plus the nested shapes inside. Leaf
7
- * fields (scalars, media, richtext, references) are omitted — the runtime
8
- * reshapes those by value-inspection. Derived from the Strapi source at scaffold
9
- * time and baked into the generated module, so nothing is inferred at runtime.
10
- *
11
- * This mirrors `BlockFieldShape` in `runtime/strapi/reshape.ts` (the composite
12
- * boundary forbids sharing the type across `scripts`/`runtime`); the
13
- * `shapes.test.ts` round-trip guards their agreement.
14
- */
15
- export interface BlockFieldShape {
16
- kind: 'single' | 'list';
17
- fields?: FieldShapes;
18
- variants?: Record<string, FieldShapes>;
19
- }
20
- export type FieldShapes = Record<string, BlockFieldShape>;
21
- /** Per-type structural manifest, keyed by Fimo type name. Types with no blocks
22
- * fields are omitted (the reshape falls back to pure value-inspection). */
23
- export declare function computeFieldShapes(schemas: CmsCollectionSchema[]): Record<string, FieldShapes>;
@@ -1,38 +0,0 @@
1
- function shapesFrom(fields) {
2
- const shapes = {};
3
- for (const [name, def] of Object.entries(fields)) {
4
- if (def.type !== 'blocks') {
5
- continue; // leaves are value-inspected by the runtime reshape
6
- }
7
- if ('oneOf' in def.schema) {
8
- const variants = {};
9
- for (const variant of def.schema.oneOf) {
10
- const nested = shapesFrom(variant.fields);
11
- if (Object.keys(nested).length > 0) {
12
- variants[variant.uid] = nested;
13
- }
14
- }
15
- shapes[name] = { kind: 'list', ...(Object.keys(variants).length > 0 ? { variants } : {}) };
16
- }
17
- else {
18
- const nested = shapesFrom(def.schema.fields);
19
- shapes[name] = {
20
- kind: def.max === 1 ? 'single' : 'list',
21
- ...(Object.keys(nested).length > 0 ? { fields: nested } : {}),
22
- };
23
- }
24
- }
25
- return shapes;
26
- }
27
- /** Per-type structural manifest, keyed by Fimo type name. Types with no blocks
28
- * fields are omitted (the reshape falls back to pure value-inspection). */
29
- export function computeFieldShapes(schemas) {
30
- const byType = {};
31
- for (const schema of schemas) {
32
- const shapes = shapesFrom(schema.fields);
33
- if (Object.keys(shapes).length > 0) {
34
- byType[schema.name] = shapes;
35
- }
36
- }
37
- return byType;
38
- }
@@ -1,31 +0,0 @@
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;
@@ -1,145 +0,0 @@
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
- }