chore: retire legacy custom CMS (content-editor.js) (MITHOME-93)
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Canceled after 0s
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Canceled after 0s
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Canceled after 0s
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Canceled after 0s
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Canceled after 0s
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Canceled after 0s
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Canceled after 0s
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Canceled after 0s
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Canceled after 0s
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Canceled after 0s
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Canceled after 0s
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Canceled after 0s
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Canceled after 0s
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Canceled after 0s
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Canceled after 0s
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Canceled after 0s
Removes the standalone, git-push-based content editor that predates
Payload CMS: content-editor.js, its scripts/cms-*.js modules, its
scripts/test-content-editor-*.js + scripts/test-cms-publish.js test
suite, scripts/markdown-render.js (only used by the editor's guide
renderer), the proto-side test doubles (cms-editor-client.test.ts,
cms-editor-shortcuts.test.ts), and the editor's own user guide
(docs/felhasznaloi-utmutato.md).
Kept: proto/src/content/*.json (still the source for
migrate-content-to-payload.ts and test fixtures for Header/Footer,
per MITHOME-96), proto/src/content/schema.js + scripts/test-content-schema.js
(still validate those JSON files), and docs/content-editor-recovery.md
(historical incident record, not user-facing tool docs).
Safety net before deletion (per user request): added
proto/scripts/export-content-snapshot.ts, a reusable Payload Local API
exporter, and ran it to produce docs/backups/payload-content-snapshot-*.json
— a full hu/en snapshot of every Global + LegalPages + Partners document
at the moment of retirement. Also confirmed no data-loss risk otherwise:
.content-backups/ (the editor's own gitignored backup dir) tops out at
2026-08-23, well before today's fresh migration run, and every JSON
edit ever made through the editor already exists as its own git commit
("content: frissítve a CMS-ből").
Updated dangling references: pre-deploy-tests.sh and
.agent/steering/testing.md (dropped the CMS test block),
.agent/workflows/deploy.md (publish flow is now Payload draft/publish,
not git push), CLAUDE.md + .agent/AGENTS.md (dropped the /cms-feature
workflow, deleted alongside it), README.md (stack description),
.agent/steering/development-rules.md (the guide-maintenance rule no
longer has a guide to maintain).
Verified: tsc, lint, proto unit tests (51 passed), root
test-content-schema.js, plane-sync unit tests, production build all
green after the deletion.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
21a0d73639
commit
12b2711168
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* MITHOME-93 biztonsági lépés: a régi egyedi CMS (content-editor.js +
|
||||
* scripts/cms-*.js) eltávolítása előtt exportálja a Payload jelenlegi
|
||||
* (publikált) szöveges tartalmát egy olvasható JSON fájlba — mindkét
|
||||
* locale-lal (hu, en) —, hogy git-committolt, ember által is átnézhető
|
||||
* biztonsági mentés maradjon a leépítés pillanatáról.
|
||||
*
|
||||
* NEM helyettesíti a git history-t (a src/content/*.json fájlok minden
|
||||
* korábbi szerkesztése megvan commit-onként), és nem helyettesíti a
|
||||
* MongoDB-t (az az élő forrás) — ez egy plusz, könnyen olvasható
|
||||
* pillanatkép a "mielőtt törlünk, mentsünk" elv jegyében.
|
||||
*
|
||||
* Futtatás (proto/ mappából, futó MongoDB-vel és beállított env-ekkel):
|
||||
* node --env-file=.env.local --import tsx scripts/export-content-snapshot.ts
|
||||
*/
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { getPayload, type Payload } from 'payload'
|
||||
import config from '../src/payload.config'
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
const outDir = path.resolve(scriptDir, '../../docs/backups')
|
||||
|
||||
const GLOBAL_SLUGS = ['home', 'about', 'services', 'contact', 'common'] as const
|
||||
const LOCALES = ['hu', 'en'] as const
|
||||
|
||||
async function exportGlobals(payload: Payload) {
|
||||
const result: Record<string, Record<string, unknown>> = {}
|
||||
for (const slug of GLOBAL_SLUGS) {
|
||||
result[slug] = {}
|
||||
for (const locale of LOCALES) {
|
||||
result[slug][locale] = await payload.findGlobal({ slug, locale })
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function exportCollection(payload: Payload, collection: 'legal-pages' | 'partners') {
|
||||
const result: Record<string, unknown> = {}
|
||||
for (const locale of LOCALES) {
|
||||
const { docs } = await payload.find({ collection, locale, limit: 1000 })
|
||||
result[locale] = docs
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
const snapshot = {
|
||||
exportedAt: new Date().toISOString(),
|
||||
reason: 'MITHOME-93 — régi CMS (content-editor.js) leépítése előtti biztonsági mentés',
|
||||
globals: await exportGlobals(payload),
|
||||
collections: {
|
||||
legalPages: await exportCollection(payload, 'legal-pages'),
|
||||
partners: await exportCollection(payload, 'partners'),
|
||||
},
|
||||
}
|
||||
|
||||
fs.mkdirSync(outDir, { recursive: true })
|
||||
const filename = `payload-content-snapshot-${snapshot.exportedAt.replace(/[:.]/g, '-')}.json`
|
||||
const outPath = path.join(outDir, filename)
|
||||
fs.writeFileSync(outPath, JSON.stringify(snapshot, null, 2), 'utf8')
|
||||
|
||||
payload.logger.info(`Snapshot kiírva: ${outPath}`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
run()
|
||||
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* Regression test for the Content Editor browser script: deleting an array
|
||||
* item via its ❌ button must reindex the remaining items, otherwise collect()
|
||||
* produces sparse arrays (null holes) that fail schema validation
|
||||
* ("$.details.services[1].specs.items[0]: string érték szükséges").
|
||||
*
|
||||
* Runs the REAL scripts/cms-editor-client.js in jsdom and clicks the actual
|
||||
* delete buttons — earlier coverage only exercised reindexItems() directly,
|
||||
* which missed that the onclick handler removed the node BEFORE looking up
|
||||
* its container (detached node → closest() === null → no reindex).
|
||||
*/
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const clientJs = fs.readFileSync(path.join(__dirname, '../../../scripts/cms-editor-client.js'), 'utf8')
|
||||
|
||||
const service = (n: number) => ({
|
||||
id: `svc-${n}`,
|
||||
title: `Szolgáltatás ${n}`,
|
||||
description: `Leírás ${n}`,
|
||||
icon: '🔧',
|
||||
features: [`feature ${n}`],
|
||||
ctaText: 'CTA',
|
||||
})
|
||||
|
||||
const makeData = () => ({
|
||||
details: {
|
||||
title: 'Részletek',
|
||||
subtitle: 'Alcím',
|
||||
services: [
|
||||
{ icon: 'a', title: 's0', description: 'd0', specs: { title: 't0', items: ['a0', 'b0', 'c0'] } },
|
||||
{ icon: 'b', title: 's1', description: 'd1', specs: { title: 't1', items: ['a1', 'b1', 'c1'] } },
|
||||
{ icon: 'c', title: 's2', description: 'd2', specs: { title: 't2', items: ['a2', 'b2', 'c2'] } },
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
function bootClient(data: unknown) {
|
||||
;(global as any).DATA = data
|
||||
;(global as any).FILE = 'services'
|
||||
;(global as any).CSRF_TOKEN = 'test-token'
|
||||
;(global as any).fetch = jest.fn()
|
||||
document.body.innerHTML = '<div id="editor"></div>'
|
||||
// sloppy-mode eval publishes the script's functions on the global object
|
||||
;(0, eval)(clientJs)
|
||||
}
|
||||
|
||||
function deleteButtonFor(dataPath: string): HTMLButtonElement {
|
||||
const field = document.querySelector(`[data-path="${CSS.escape(dataPath)}"]`) as HTMLElement
|
||||
expect(field).not.toBeNull()
|
||||
const wrap = field.closest('.str-item') as HTMLElement
|
||||
expect(wrap).not.toBeNull()
|
||||
return wrap.querySelector('.btn-del') as HTMLButtonElement
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
delete (global as any).DATA
|
||||
delete (global as any).FILE
|
||||
delete (global as any).CSRF_TOKEN
|
||||
})
|
||||
|
||||
describe('Content Editor client delete/reindex', () => {
|
||||
it('deleting a nested string array item keeps the remaining items dense', () => {
|
||||
const data = makeData()
|
||||
bootClient(data)
|
||||
|
||||
deleteButtonFor('details.services[1].specs.items[0]').click()
|
||||
|
||||
const collected = (global as any).collect()
|
||||
expect(collected.details.services[1].specs.items).toEqual(['b1', 'c1'])
|
||||
expect(collected.details.services[0].specs.items).toEqual(['a0', 'b0', 'c0'])
|
||||
expect(collected.details.services[2].specs.items).toEqual(['a2', 'b2', 'c2'])
|
||||
})
|
||||
|
||||
it('deleting an object card reindexes the outer array', () => {
|
||||
const data = makeData()
|
||||
bootClient(data)
|
||||
|
||||
const cardHeader = Array.from(document.querySelectorAll('.card-header'))
|
||||
.find(h => h.textContent === 'details.services[1]') as HTMLElement
|
||||
expect(cardHeader).not.toBeNull()
|
||||
const card = cardHeader.closest('.obj-card') as HTMLElement
|
||||
;(card.querySelector('.btn-del-card') as HTMLButtonElement).click()
|
||||
|
||||
const collected = (global as any).collect()
|
||||
expect(collected.details.services).toHaveLength(2)
|
||||
expect(collected.details.services[0].title).toBe('s0')
|
||||
expect(collected.details.services[1].title).toBe('s2')
|
||||
expect(collected.details.services[1].specs.items).toEqual(['a2', 'b2', 'c2'])
|
||||
})
|
||||
})
|
||||
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* Regression tests for the Content Editor keyboard shortcuts (MITHOME-75).
|
||||
* Runs the real scripts/cms-editor-client.js in jsdom, dispatches actual
|
||||
* KeyboardEvents and asserts:
|
||||
* 1. Ctrl+S calls save (fetch /save) and the browser save dialog is
|
||||
* suppressed (preventDefault)
|
||||
* 2. Ctrl+P calls publish
|
||||
* 3. Ctrl+Shift+V opens the versions page in a new tab
|
||||
* 4. '?' opens the shortcuts overlay, Esc closes it
|
||||
* 5. plain 's' inside an input does NOT trigger save
|
||||
*/
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const clientJs = fs.readFileSync(path.join(__dirname, '../../../scripts/cms-editor-client.js'), 'utf8')
|
||||
+ '\n' + fs.readFileSync(path.join(__dirname, '../../../scripts/cms-editor-shortcuts.js'), 'utf8')
|
||||
|
||||
const makeData = () => ({
|
||||
hero: { title: 'T', subtitle: 'S', description: 'D' },
|
||||
})
|
||||
|
||||
function press(target: Document | Element, init: KeyboardEventInit) {
|
||||
const ev = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...init })
|
||||
target.dispatchEvent(ev)
|
||||
return ev
|
||||
}
|
||||
|
||||
const flush = () => new Promise(r => setTimeout(r, 0))
|
||||
|
||||
// Globals must exist BEFORE the eval — the client script boots immediately
|
||||
// (render(DATA, …)). The eval runs ONCE: every eval would add another keydown
|
||||
// listener to the shared jsdom document, and the toggle-style overlay handler
|
||||
// would then open/close itself multiple times per keypress.
|
||||
;(global as any).DATA = makeData()
|
||||
;(global as any).FILE = 'home'
|
||||
;(global as any).CSRF_TOKEN = 'boot'
|
||||
;(global as any).CONTENT_HASH = 'x'.repeat(64)
|
||||
;(global as any).fetch = jest.fn(async () => ({ status: 200, ok: true, json: async () => ({ ok: true }) }))
|
||||
;(global as any).window = global
|
||||
document.body.innerHTML = '<div id="editor"></div>'
|
||||
;(0, eval)(clientJs)
|
||||
|
||||
describe('Content Editor keyboard shortcuts', () => {
|
||||
let fetchCalls: Array<{ url: string; init?: RequestInit }>
|
||||
|
||||
beforeEach(() => {
|
||||
fetchCalls = []
|
||||
;(global as any).DATA = makeData()
|
||||
;(global as any).FILE = 'home'
|
||||
;(global as any).CSRF_TOKEN = 'test-csrf'
|
||||
;(global as any).CONTENT_HASH = 'x'.repeat(64)
|
||||
;(global as any).fetch = jest.fn(async (url: string, init?: RequestInit) => {
|
||||
fetchCalls.push({ url, init })
|
||||
return { status: 200, ok: true, json: async () => ({ ok: true, contentHash: 'y'.repeat(64) }) }
|
||||
})
|
||||
;(global as any).window = global
|
||||
;(window as any).open = jest.fn()
|
||||
document.body.innerHTML = '<div id="editor"></div><span id="saveStatus" style="display:none"></span><button id="publishBtn">pub</button>'
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
delete (global as any).DATA
|
||||
delete (global as any).FILE
|
||||
delete (global as any).CSRF_TOKEN
|
||||
delete (global as any).CONTENT_HASH
|
||||
})
|
||||
|
||||
it('Ctrl+S saves via fetch and suppresses the browser save dialog', () => {
|
||||
const ev = press(document, { key: 's', ctrlKey: true })
|
||||
expect(ev.defaultPrevented).toBe(true)
|
||||
expect(fetchCalls.length).toBeGreaterThanOrEqual(1)
|
||||
expect(fetchCalls[0].url).toContain('/save?file=home')
|
||||
})
|
||||
|
||||
it('Cmd+P publishes', async () => {
|
||||
const ev = press(document, { key: 'p', metaKey: true })
|
||||
expect(ev.defaultPrevented).toBe(true)
|
||||
await flush() // publish awaits save() before its own fetch
|
||||
expect(fetchCalls.some(c => c.url === '/publish')).toBe(true)
|
||||
})
|
||||
|
||||
it('Ctrl+Shift+V opens the versions page in a new tab', () => {
|
||||
const ev = press(document, { key: 'V', ctrlKey: true, shiftKey: true })
|
||||
expect(ev.defaultPrevented).toBe(true)
|
||||
expect((window as any).open).toHaveBeenCalledWith('/versions?file=home', '_blank')
|
||||
})
|
||||
|
||||
it("'?' opens the shortcuts overlay and Esc closes it", () => {
|
||||
press(document, { key: '?' })
|
||||
expect(document.getElementById('shortcuts-overlay')).not.toBeNull()
|
||||
press(document, { key: 'Escape' })
|
||||
expect(document.getElementById('shortcuts-overlay')).toBeNull()
|
||||
})
|
||||
|
||||
it('plain typing in an input never triggers save', () => {
|
||||
const input = document.createElement('input')
|
||||
document.body.appendChild(input)
|
||||
const ev = press(input, { key: 's' })
|
||||
expect(ev.defaultPrevented).toBe(false)
|
||||
expect(fetchCalls.length).toBe(0)
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,9 @@
|
||||
// Shared runtime schema for the content JSON files.
|
||||
// Kept dependency-free so it can run in both Next.js and content-editor.js.
|
||||
// Shared runtime schema for the content JSON files (proto/src/content/*.json).
|
||||
// Kept dependency-free — used by src/content/index.ts (test fixtures for
|
||||
// Header/Footer, MITHOME-96) and scripts/test-content-schema.js. The JSON
|
||||
// files themselves remain the source for scripts/migrate-content-to-payload.ts.
|
||||
// The standalone content-editor.js CMS that used to run this too was retired
|
||||
// in MITHOME-93 (superseded by Payload CMS).
|
||||
const string = { type: 'string' };
|
||||
const boolean = { type: 'boolean' };
|
||||
const array = items => ({ type: 'array', items });
|
||||
|
||||
Reference in New Issue
Block a user