@open_mind/editor-sdk 0.2.0 → 0.2.1

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 CHANGED
@@ -5,10 +5,20 @@ Publicly downloadable React and Next.js integration SDK for the Fieldmatch visua
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install @open_mind/editor-sdk
8
+ npm install @open_mind/editor-sdk@0.2.1
9
+ npx fieldmatch-editor-sdk-init
10
+ ```
11
+
12
+ No npm token or custom registry is required. The initializer also creates or supplements `.env.local` without overwriting existing values. Fill in:
13
+
14
+ ```ini
15
+ NEXT_PUBLIC_FIELDMATCH_SITE_ID=your-site-id
16
+ NEXT_PUBLIC_FIELDMATCH_EDITOR_ORIGIN=https://fieldmatch-canvas-editor.vercel.app
17
+ CMS_INTERNAL_URL=https://fieldmatch-canvas-cms.vercel.app
18
+ REVALIDATION_SECRET=a-shared-secret-configured-in-the-site-record
9
19
  ```
10
20
 
11
- No npm token or custom registry is required. Installation also:
21
+ For a Next.js App Router project, it also creates `app/api/revalidate/route.ts` when that route is absent. Configure the matching revalidation URL and secret in the site's Fieldmatch record. It also:
12
22
 
13
23
  - installs the repository skill at `.agents/skills/fieldmatch-editable-components`;
14
24
  - creates `FIELDMATCH_EDITOR.md`, the integration registry and checklist; and
@@ -98,7 +108,7 @@ Field IDs are persistent content keys. Use stable names such as `page.section.el
98
108
 
99
109
  Image fields support the Fieldmatch media library. Video fields currently accept a direct HTTP(S) URL; this version does not upload video files.
100
110
 
101
- See the generated `FIELDMATCH_EDITOR.md` and use the `$fieldmatch-editable-components` skill for the full integration workflow, CSP, preview, and verification guidance.
111
+ The `$fieldmatch-editable-components` skill is mandatory whenever adding or changing visitor-visible text, links, images, or videos. It ensures every such item is wrapped using the matching SDK component. See the generated `FIELDMATCH_EDITOR.md` for the full integration workflow, CSP, preview, and verification guidance.
102
112
 
103
113
  ## License
104
114
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open_mind/editor-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Public integration SDK for connecting React and Next.js sites to the Fieldmatch visual editor.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { cp, mkdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { cp, mkdir, readFile, stat, writeFile } from 'node:fs/promises'
4
4
  import { dirname, resolve } from 'node:path'
5
5
  import { fileURLToPath } from 'node:url'
6
6
 
@@ -9,12 +9,13 @@ const targetRoot = resolve(process.env.INIT_CWD || process.cwd())
9
9
  const force = process.argv.includes('--force')
10
10
  const startMarker = '<!-- fieldmatch-editor-sdk:start -->'
11
11
  const endMarker = '<!-- fieldmatch-editor-sdk:end -->'
12
+ const environmentTemplate = `# Fieldmatch visual-editor configuration\n# Fill the blank values. Keep this file out of version control.\nNEXT_PUBLIC_FIELDMATCH_SITE_ID=\nNEXT_PUBLIC_FIELDMATCH_EDITOR_ORIGIN=https://fieldmatch-canvas-editor.vercel.app\nCMS_INTERNAL_URL=https://fieldmatch-canvas-cms.vercel.app\nREVALIDATION_SECRET=\n`
12
13
  const managedBlock = `${startMarker}
13
14
  ## Fieldmatch editable content
14
15
 
15
- When creating or changing content that should be editable through Fieldmatch, use the \`$fieldmatch-editable-components\` skill installed at \`.agents/skills/fieldmatch-editable-components\`.
16
+ **Mandatory:** before creating or changing any visitor-visible text, link, image, or video, read and use the \`$fieldmatch-editable-components\` skill installed at \`.agents/skills/fieldmatch-editable-components\`. Wrap every such item with the matching Fieldmatch SDK component.
16
17
 
17
- Read and maintain \`FIELDMATCH_EDITOR.md\` as the integration registry. Only content explicitly intended for customer editing should use the SDK; keep layout, styling, behavior, authorization, prices, and application state in code unless requirements say otherwise.
18
+ Read and maintain \`FIELDMATCH_EDITOR.md\` as the integration registry. Keep layout, styling, interaction behavior, authorization, calculations, and application state in code; do not use those as a reason to leave their visible text, links, images, or videos unwrapped.
18
19
  ${endMarker}`
19
20
 
20
21
  async function hasDirectDependency() {
@@ -27,6 +28,47 @@ async function hasDirectDependency() {
27
28
  }
28
29
  }
29
30
 
31
+ async function ensureEnvironmentFile() {
32
+ const environmentTarget = resolve(targetRoot, '.env.local')
33
+ let existing = ''
34
+ try {
35
+ existing = await readFile(environmentTarget, 'utf8')
36
+ } catch {
37
+ // Start with the managed defaults when the project has no local environment file.
38
+ }
39
+
40
+ const missing = environmentTemplate
41
+ .split('\n')
42
+ .filter((line) => line && !line.startsWith('#'))
43
+ .filter((line) => !new RegExp(`^${line.split('=')[0]}=`, 'm').test(existing))
44
+
45
+ if (!existing) {
46
+ await writeFile(environmentTarget, environmentTemplate)
47
+ } else if (missing.length) {
48
+ await writeFile(environmentTarget, `${existing.trimEnd()}\n\n# Fieldmatch visual-editor configuration\n${missing.join('\n')}\n`)
49
+ }
50
+ }
51
+
52
+ async function ensureNextRevalidationRoute() {
53
+ for (const applicationRoot of ['app', 'src/app']) {
54
+ const routeTarget = resolve(targetRoot, applicationRoot, 'api/revalidate/route.ts')
55
+ try {
56
+ await readFile(routeTarget, 'utf8')
57
+ return 'existing'
58
+ } catch {
59
+ try {
60
+ if (!(await stat(resolve(targetRoot, applicationRoot))).isDirectory()) continue
61
+ } catch {
62
+ continue
63
+ }
64
+ await mkdir(dirname(routeTarget), { recursive: true })
65
+ await cp(resolve(packageRoot, 'templates/next-revalidate-route.ts'), routeTarget)
66
+ return 'created'
67
+ }
68
+ }
69
+ return 'not-next-app-router'
70
+ }
71
+
30
72
  async function install() {
31
73
  if (process.env.FIELDMATCH_SKIP_AGENT_SETUP === '1') return
32
74
  if (targetRoot === packageRoot) return
@@ -39,6 +81,9 @@ async function install() {
39
81
  force: true,
40
82
  })
41
83
 
84
+ await ensureEnvironmentFile()
85
+ const revalidationRoute = await ensureNextRevalidationRoute()
86
+
42
87
  const guideTarget = resolve(targetRoot, 'FIELDMATCH_EDITOR.md')
43
88
  try {
44
89
  await readFile(guideTarget, 'utf8')
@@ -64,11 +109,11 @@ async function install() {
64
109
  }
65
110
  await writeFile(agentsTarget, agents)
66
111
 
67
- process.stdout.write('[Fieldmatch] Installed agent skill and integration instructions. Restart your coding agent so it reloads AGENTS.md.\n')
112
+ const routeMessage = revalidationRoute === 'created' ? ' Added app/api/revalidate/route.ts.' : revalidationRoute === 'existing' ? ' Reused the existing revalidation route.' : ''
113
+ process.stdout.write(`[Fieldmatch] Installed the required agent skill, integration instructions, and missing .env.local entries.${routeMessage} Fill NEXT_PUBLIC_FIELDMATCH_SITE_ID and REVALIDATION_SECRET, then restart your coding agent.\n`)
68
114
  }
69
115
 
70
116
  install().catch((error) => {
71
117
  process.stderr.write(`[Fieldmatch] Agent setup skipped: ${error instanceof Error ? error.message : String(error)}\n`)
72
118
  process.exitCode = 0
73
119
  })
74
-
@@ -5,16 +5,16 @@ description: Integrate or update React and Next.js components that should be edi
5
5
 
6
6
  # Fieldmatch Editable Components
7
7
 
8
- Connect deliberately chosen website content to Fieldmatch while preserving the site's structure, design, accessibility, and local fallbacks.
8
+ Connect every visitor-visible text, link, image, and video to Fieldmatch while preserving the site's structure, design, accessibility, and local fallbacks.
9
9
 
10
10
  ## Workflow
11
11
 
12
- 1. Read `FIELDMATCH_EDITOR.md`. If it is absent, create it from the SDK's documented project guide structure before integrating fields.
12
+ 1. This skill is mandatory whenever a visitor-visible text, link, image, or video is created or changed. Read `FIELDMATCH_EDITOR.md` first. If it is absent, run `npx fieldmatch-editor-sdk-init` before integrating fields.
13
13
  2. Inspect the installed SDK version, existing server content fetch, preview handling, and nearest `EditorProvider`. Reuse one provider around the editable page area; never add a provider per field.
14
- 3. Identify only content explicitly intended for customer editing. Keep layout, CSS, component behavior, authorization, prices, calculated values, and application state in code unless the requirement expressly includes them.
14
+ 3. Wrap every visitor-visible text, link, image, and video. Keep layout, CSS, component behavior, authorization, prices, calculated values, and application state in code, but still wrap the visible labels, copy, links, images, and videos that those features render.
15
15
  4. Assign each field a stable, unique ID in `page.section.element` form. Do not silently rename or reuse IDs. An ID rename is a content migration.
16
16
  5. Choose the matching SDK component and preserve the original DOM semantics, classes, accessibility attributes, behavior, and meaningful local fallback.
17
- 6. Update the field registry in `FIELDMATCH_EDITOR.md` with the ID, page, type, source component, fallback, and limitations.
17
+ 6. Update the field registry in `FIELDMATCH_EDITOR.md` with every ID, page, type, source component, fallback, and limitation.
18
18
  7. Verify normal rendering, editor selection, draft persistence, publish behavior, revalidation, and secret handling.
19
19
 
20
20
  ## Map content to components
@@ -1,5 +1,11 @@
1
1
  # Fieldmatch component patterns
2
2
 
3
+ ## Required coverage
4
+
5
+ Wrap every visitor-visible text, link, image, and video with the corresponding SDK component. Keep only layout, styling, interactivity, authorization, calculations, and state in application code. If one of those features renders visible copy or media, wrap that visible value too.
6
+
7
+ Run `npx fieldmatch-editor-sdk-init` before integration. It creates or supplements `.env.local` with `NEXT_PUBLIC_FIELDMATCH_SITE_ID`, `NEXT_PUBLIC_FIELDMATCH_EDITOR_ORIGIN`, `CMS_INTERNAL_URL`, and `REVALIDATION_SECRET`; supply the deployment-specific values before running the site.
8
+
3
9
  ## Server content and local fallbacks
4
10
 
5
11
  Fetch content on the server and merge it with local defaults. Public requests use `published`; only authenticated preview flows may request `draft` with a server-only token.
@@ -109,4 +115,3 @@ Publishing should call a protected site endpoint that verifies the shared revali
109
115
  4. Confirm the public site has not changed before publishing.
110
116
  5. Publish, then confirm revalidation makes the new content public.
111
117
  6. Inspect the client bundle and network requests for leaked tokens or secrets.
112
-
@@ -1,6 +1,6 @@
1
1
  # Fieldmatch editor integration
2
2
 
3
- This file is the project-specific source of truth for content connected to `@open_mind/editor-sdk`. Keep it current whenever editable content is added, renamed, moved, or removed. Do not place tokens or secrets here.
3
+ This file is the project-specific source of truth for content connected to `@open_mind/editor-sdk`. Keep it current whenever visitor-visible text, links, images, or videos are added, renamed, moved, or removed. Do not place tokens or secrets here.
4
4
 
5
5
  ## Project configuration
6
6
 
@@ -21,10 +21,11 @@ This file is the project-specific source of truth for content connected to `@ope
21
21
 
22
22
  - Render one `EditorProvider` around the editable area; do not create a provider per field.
23
23
  - Fetch published content for normal visitors. Fetch draft content only in authenticated preview/editor mode.
24
+ - **Mandatory:** use the `$fieldmatch-editable-components` skill whenever adding or changing visitor-visible text, links, images, or videos. Wrap every such item with the matching SDK component.
24
25
  - Every editable field must have a stable, unique ID such as `home.hero.title` and a meaningful local fallback.
25
26
  - Do not silently rename or reuse an existing field ID. Treat an ID change as a content migration.
26
27
  - Use `EditableText` for text, textarea, rich-text strings, and button labels; `EditableLink` for destinations; `EditableImage` for images; and `EditableVideo` for direct video URLs.
27
- - Keep structure, styling, accessibility, behavior, permissions, prices, and application state in code unless the product requirement explicitly makes them editable content.
28
+ - Keep structure, styling, accessibility, behavior, permissions, prices, and application state in code. Their visible text, links, images, and videos remain editable fields.
28
29
  - When wrapping custom components, make sure the final DOM element receives the props and event handlers supplied by the SDK.
29
30
  - Image selection supports the Fieldmatch media library. Video currently accepts a direct HTTP(S) URL; video upload and poster editing are not provided by this SDK version.
30
31
  - Keep the site CSP `frame-ancestors` restricted to the exact Fieldmatch editor origin. Never use a wildcard.
@@ -37,4 +38,3 @@ This file is the project-specific source of truth for content connected to `@ope
37
38
  - [ ] Draft edits survive a refresh and remain invisible on the public site before publishing.
38
39
  - [ ] Publish updates the public site and triggers revalidation successfully.
39
40
  - [ ] No CMS token, preview token, or revalidation secret is shipped to the browser.
40
-
@@ -0,0 +1,21 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto'
2
+ import { revalidatePath } from 'next/cache'
3
+ import { NextResponse } from 'next/server'
4
+
5
+ export async function POST(request: Request) {
6
+ const body = await request.text()
7
+ const secret = process.env.REVALIDATION_SECRET?.trim()
8
+
9
+ if (!secret && process.env.NODE_ENV === 'production') {
10
+ return NextResponse.json({ error: 'Revalidation is not configured.' }, { status: 503 })
11
+ }
12
+
13
+ const expected = createHmac('sha256', secret || 'development-revalidation-secret').update(body).digest()
14
+ const received = Buffer.from(request.headers.get('x-agency-signature') ?? '', 'hex')
15
+ if (received.length !== expected.length || !timingSafeEqual(received, expected)) {
16
+ return NextResponse.json({ error: 'Invalid webhook signature.' }, { status: 403 })
17
+ }
18
+
19
+ revalidatePath('/')
20
+ return NextResponse.json({ revalidated: true })
21
+ }