Skip to content

fix(#7410): add proper typings for registerWidget and CmsBackendClass - #7411

Open
fgnass wants to merge 34 commits into
decaporg:mainfrom
fgnass:fix/7410
Open

fix(#7410): add proper typings for registerWidget and CmsBackendClass #7411
fgnass wants to merge 34 commits into
decaporg:mainfrom
fgnass:fix/7410

Conversation

@fgnass

@fgnass fgnass commented Feb 24, 2025

Copy link
Copy Markdown
Contributor

This is a follow-up PR that supersedes #7402 and fixes #7410.

It also addresses the // TODO: type properly comment that was previously in index.d.ts

In general, this shows how we can gradually migrate the codebase to TypeScript.

@fgnass
fgnass requested a review from a team as a code owner February 24, 2025 17:32
@martinjagodic

Copy link
Copy Markdown
Member

That is a big diff to review 😅 We are working on some other features at the moment, I really hope to get to your PRs soon!

@fgnass

fgnass commented Feb 25, 2025

Copy link
Copy Markdown
Contributor Author

I know 😅 No worries, I'm happily using my fork in the meantime. I squashed the commits of the parent PR to at least reduce the number of commits in this one and make it possible to review both in sequence. Let me know if there is anything else I can do to make your life easier.

yanthomasdev and others added 6 commits May 14, 2026 16:43
Resolved conflicts:

- package-lock.json: deleted, main migrated the repo to pnpm.
- package.json / pnpm-workspace.yaml: took main's pnpm+catalog form, kept the
  PR's `tsc --build` type-check and tsbuildinfo cleanup, bumped the catalog's
  typescript to ^5.3.0 and added @types/semaphore. Kept main's type-check step
  in test:ci.
- tsconfig.json: kept the PR's composite/project-references layout, adopted
  main's automatic JSX runtime (jsx: react-jsx, jsxImportSource: @emotion/react).
- decap-cms-core/package.json: main's catalog deps, minus the
  decap-cms-editor-component-image peer dep the PR inverted.
- decap-cms-editor-component-image: kept the PR's typed index.tsx but dropped
  the React import, which main no longer needs; decap-cms-core is a
  workspace:* devDependency.
- decap-cms-backend-test/src/implementation.ts: took main's moveFile refactor.
- widget-markdown slate.spec.js: kept the PR's relocated test-helpers path plus
  main's jsxRuntime/eslint pragmas.
Merging main brought two things the PR's declaration emit had never met:
pnpm's isolated node_modules and immutable 4. `tsc --build` and
`nx run-many -t build` both failed with 9 errors; each is fixed at the source
rather than by hoisting node_modules.

Undeclared dependencies that only resolved via npm's flat tree:
- decap-cms-core imports `Pluggable` from `unified`
- decap-cms-backend-gitlab and decap-cms-backend-github reach `graphql` and
  `apollo-link` types through apollo-client
- decap-cms-widget-markdown's serializers infer `@types/unist` nodes

Inferred types that TS cannot name under pnpm (TS2742) now carry explicit
annotations, which is also what makes the emitted .d.ts portable — they
reference `graphql`, `apollo-link` and `unist` by package name instead of a
`.pnpm/...` realpath:
- gitlab queries: `DocumentNode`
- GraphQLAPI.mutate: `Promise<FetchResult<OperationVariables>>`
- markdown serializers: `@returns {import('unist').Node}`
- DecapCmsApp: `CMS`

Two type errors were real:
- backend-test moveFile, added by main, passes the now-optional
  `RepoFile.content` to writeFile, same as the other call site.
- createEditorComponent claims to return EditorComponentOptions, but converts
  `fields` to Immutable and consumers read it that way (`fields.update(...)`,
  `field.get('widget')`). immutable 3 typed `fromJS` as `any` and hid this;
  immutable 4 does not. Named that shape `RegisteredEditorComponent` and used
  it for the registry and `getEditorComponents()` instead of casting the lie
  away.
@netlify

netlify Bot commented Sep 8, 2026

Copy link
Copy Markdown

Deploy Preview for decap-cms ready!

Name Link
🔨 Latest commit c3145e2
🔍 Latest deploy log https://app.netlify.com/projects/decap-cms/deploys/6aa1118b74778d0008ddb260
😎 Deploy Preview https://deploy-preview-7411--decap-cms.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

The PR built the machinery to generate declarations from source but stopped
short in two ways: it relaxed the checks that would have exercised that
machinery, and it kept a hand-written interface as the gatekeeper, so the
published API surface could still omit APIs that exist.

Strictness, restored to what main has today:

- decap-cms-core had noImplicitAny, strictNullChecks and strictFunctionTypes
  turned off, hiding 47 errors. All three are back. Most were unannotated
  parameters in the newly converted registry.ts; the rest were real:
  - actions/config.ts declared `let collectionI18n: CmsI18nConfig` and then
    assigned undefined to it (same for fileI18n)
  - actions/entries.ts leaked `undefined` out of createEmptyDraftData through
    an over-broad reduce annotation; the reducer only ever returns the
    DraftEntryData accumulator, so it is now typed as such (the large diff is
    prettier reindenting the body, not a logic change)
  - registerWidget could store a widget with no control component; it now
    reports that the same way the object branch already did
  - getWidgets ran a `produce` that never mutated its draft, purely to have
    somewhere to put an `any`; plain map does the same thing
- .eslintrc.js disabled @typescript-eslint/no-explicit-any repo-wide with a
  "TODO enable in future". It covered 10 violations in 3 files, all added by
  this PR. Rule restored, violations removed, zero left.

Types that did not match the implementation:

- GetAssetFunction described a one-argument function returning a plain object.
  The real one, boundGetAsset in actions/media.ts, takes (path, field) and
  returns an AssetProxy. Every real call site was a type error, which is why
  editor-component-image annotated its own parameters as `any` — the reference
  component opting out of the types it was demonstrating. Type fixed, `any`s
  gone, and that component is now the acceptance test for it.
- CmsEventListener.handler declared one parameter; invokeEvent passes the
  registration options as a second.
- PreviewStyleOptions.raw was required even though the options argument is not.
- CmsRegistry.mediaLibraries did not include the options registerMediaLibrary
  stores alongside each library.
- toPreview's `fields` is an Immutable List, and is absent when a component is
  previewed outside the editor.

Making the generated typings authoritative:

- `DecapCmsCore` now uses `satisfies CMS` instead of a `: CMS` annotation. An
  annotation only checks the implementation against the interface; it cannot
  catch the interface being too narrow. That is how getWidgets,
  getEventListeners, removeEventListener, invokeEvent, getCustomFormats,
  getCustomFormatsExtensions and getCustomFormatsFormatters — seven APIs that
  have always existed — were invisible to TypeScript users on main and stayed
  invisible under this PR. They resolve now, and CMS lists them too.
- scripts/types-fixture type-checks the built declarations the way an external
  consumer would, and runs in CI after build:demo. Verified it fails when an
  API is dropped from the public export.
- scripts/test-types-coverage.mjs asserts every published package is wired into
  the declaration build, so a new package cannot silently fall out of
  `tsc --build` the way four already had.

Coverage and cleanup:

- decap-cms-backend-forgejo, decap-cms-ui-auth, decap-cms-widget-richtext and
  decap-cms-widget-uuid post-date the PR and were never added to it, so they
  emitted no types and, once the root tsconfig moved to `include: []` plus
  references, were not type-checked at all. All four are wired in now.
- Deleted three stale hand-written index.d.ts files. core's was resurrected by
  an earlier merge and duplicated src/types; lib-auth's was already dead;
  ui-auth's imported from `decap-cms-lib-util/src`, which that package does not
  publish, so it was broken for consumers. decap-cms-app's `files` still listed
  an index.d.ts the PR had deleted.
- `pnpm develop` was broken in every package that gained build:types: `--watch`
  landed on `run-p`, which rejects it. Present before the merge; no CI job runs
  develop, so nothing caught it.
- Moved widget-markdown's test helper back out of src/. The PR moved it in but
  left widget-richtext's identical copy alone, and under src/ it shipped to npm
  in dist/ with a generated h.d.ts.
martinjagodic added a commit that referenced this pull request Sep 9, 2026
Cypress compiles cypress.config.ts with ts-node, which runs in CommonJS and so
forces `module: CommonJS`. The root tsconfig sets `moduleResolution: nodenext`,
and since TypeScript 5.0 that pair is rejected outright:

  error TS5110: Option 'module' must be set to 'NodeNext' when option
  'moduleResolution' is set to 'NodeNext'.

Cypress reports it as "Your configFile is invalid" and every e2e shard fails
before running a single test. It does not reproduce on main because main is
still on TypeScript 4.9, which did not enforce the constraint; bumping the
catalog to ^5.3.0 for `satisfies` surfaced it.

Give ts-node a matching pair of its own, scoped to ts-node only so nothing
else changes. Same fix as fgnass's in #7411.

Co-Authored-By: Felix Gnass <fgnass@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Types for registerWidget don't match the implementation

3 participants