Skip to content

fix(api): reconcile plugin tables on upgrade and skip a missing one on uninstall - #3

Merged
zscontributor merged 19 commits into
developfrom
fix/plugin-upgrade-uninstall-stale-action
Jul 31, 2026
Merged

fix(api): reconcile plugin tables on upgrade and skip a missing one on uninstall#3
zscontributor merged 19 commits into
developfrom
fix/plugin-upgrade-uninstall-stale-action

Conversation

@zscontributor

@zscontributor zscontributor commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Note on scope: develop was 17 commits behind main when this was opened, so this PR carries that backlog plus the 2 new commits described below. The new work is feat(plugins): enhance plugin upgrade handling… onward — everything before it is already on main and already deployed.

Three faults found while shipping a plugin version that introduces a new table. Each one made the previous one harder to see.

1. An upgrade of a RUNNING plugin never ran its schema

ensurePluginTables + setup() run on activation, and nobody activates a plugin that is already active — the admin only activates if (!isActive). Installing a new version over a running one swapped versionId and nothing else, so a table the new version declares was never created.

What the operator saw: the new admin screen answered 404, because the generic plugin resource page turns any 403/404/500 from the endpoint into notFound() — a missing table is indistinguishable from a missing screen.

install now runs the same two steps activation runs when the row was ACTIVE and the version changed. Both are idempotent (CREATE TABLE IF NOT EXISTS, ADD COLUMN IF NOT EXISTS, a setup() that seeds only what is missing) — this is dbDelta on upgrade. If the new version cannot start, the install stands, the plugin is FAILED, and its own message comes back in error rather than a clean "ok" the operator discovers is a lie on the live site.

2. Uninstall — the way OUT of a broken install — needed the tables to exist

purgePluginSiteData runs DELETE for every table the manifest declares. Against a table that was never created that is 42P01, surfaced as "Internal server error" with the plugin still installed. It now probes for the table and skips a missing one: nothing to delete is not a failure, it is the answer.

3. …and the first version of that probe was a 500 of its own

SELECT to_regclass($1) returns type regclass, which Prisma's pg adapter cannot deserialize (UnsupportedNativeDataType / P2010). The NULL test now happens in Postgres and only a boolean crosses the wire. Mocked unit tests cannot catch a driver-level type fault, so the test asserts the SQL shape instead, and the query was run against a real database.

Also here

admin.error.* — a Server Action id is baked into the page at build time, so a tab left open across a deploy posts an id the new build has never heard of. Next's message reads like a fault in the software; it is neither, and Retry is the most dangerous button on that screen — it re-sends a dead id from stale JavaScript for an action that has usually already run (the uninstall above completed; only the response path failed). The boundary now recognises it and offers a full reload.

Tests

cms-api 745, admin-web 323, i18n 118 — green; every package typechecks.

  • plugins.controller.test.ts — upgrade-while-active reconciles; an inactive install is left alone; same version re-consented does nothing; a failing setup marks FAILED and reports.
  • plugins.service.test.ts — purge deletes only this site's rows; skips a table that was never created; asks Postgres for a boolean, never for an oid.
  • error.test.tsx — stale action offers reload and never calls reset(); an ordinary failure still retries and still shows its message.

Z-SOFT Developers and others added 19 commits July 30, 2026 10:41
…; clarify job count, add changelog details, and enhance localization explanations
- Implemented `sortHref` function to manage sorting behavior in list URLs.
- Created `ResourceForm` component for creating and editing plugin resources.
- Added tests for detail fields, including sorting and filtering functionality.
- Developed comprehensive tests for `PluginAdminController` to ensure correct behavior for detail and list operations, including permission checks and input validation.
The admin answers at /admin on every tenant hostname, so the login page
already knows which site it belongs to — it just had no session to ask
with, and greeted everyone with the same Z-CMS mark on every domain.

- cms-api: GET /public/sites/branding?hostname= returns { siteId, name,
  brand, host }. Public and rate limited (120/min/IP): the caller must
  know a hostname to ask, and every field is already on that hostname's
  public home page. Unlike RenderService.resolveHost it answers for a
  site in ANY status — a draft site's owner still has to sign in.
  Cross-tenant lookup via the system client, www/apex folded, cached ten
  minutes under cms:brand:<host>.
- CacheService.forgetHosts purges the new key alongside cms:host:<host>,
  so a renamed site or a new logo shows on the next reload rather than
  whenever the TTL lapses.
- admin-web: the login page resolves x-forwarded-host through that
  endpoint and leads with the site's logo and name, plus a link out to
  the site's front page. Logo URLs are vetted (http/https or a path on
  this origin) because they are values out of a JSON column. Anything
  that does not resolve — the dedicated admin hostname, an unregistered
  domain, an API outage — renders exactly the page it always was.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Added optional `group` field to `FormFieldSchema` for organizing fields.
- Introduced `FormGroupSchema` to define groups of fields with labels and actions.
- Updated `FormDefinitionSchema` to include optional groups.
- Enhanced validation logic to check for duplicate group IDs and ensure fields reference valid groups.
- Implemented `checkFormField` and `checkFormValues` functions for improved client-side validation.
- Added tests for new validation logic and form group functionality.

feat(reference-field): implement reference field component for better user experience

- Created `ReferenceField` component to allow users to search and select references.
- Integrated debounced fetching of options from the server based on user input.
- Improved accessibility and user interaction with clear labels and loading states.

feat(return-to): implement return-to functionality for record navigation

- Added `withReturnTo` and `returnHref` functions to manage navigation back to lists with preserved query parameters.
- Implemented `searchOf` function to normalize search parameters for safe URL handling.

test: add comprehensive tests for form validation and return-to functionality

- Developed tests for `checkFormField` and `checkFormValues` to ensure accurate error reporting.
- Created tests for `return-to` utility functions to validate query parameter handling.
- Added tests for `FormIsland` component to verify inline validation and group behavior.
- Added "fieldOutOfRange" error message in English, Japanese, and Vietnamese locales.
- Introduced "newTitle" and "referenceForbidden" messages in English, Japanese, and Vietnamese plugin locales.

feat(plugin-sdk): enhance plugin resource column functionality

- Added `editable` property to `PluginResourceColumn` interface to allow inline editing of list columns.
- Introduced `columnBounds` property to define numeric column limits (min/max) for better data validation.
- Implemented `pluginColumnBounds` function to retrieve declared bounds for numeric columns.
- Updated `coercePluginRow` function to validate numeric inputs against declared bounds, adding range error handling.

feat(admin-web): create new record creation panel and page

- Implemented `CreatePanel` component for handling new record creation with proper routing and state management.
- Developed `PluginRecordCreatePage` to manage the create screen for plugin resources, including form handling and navigation.

feat(site-runtime): add form pick functionality for plugin forms

- Created `form-pick` module to manage item selection and quantity for forms, enhancing user interaction with plugin-driven forms.
- Implemented tests for form pick functionality to ensure correct behavior and integration with local storage.

test: add comprehensive tests for new features

- Added tests for `form-pick` functionality to validate item selection and quantity management.
- Created tests for `plugin-query` to ensure correct attribute handling and data binding in dynamic rows.
…on fixes

- Added detailed comments to clarify the hydration process and its implications on React rendering.
- Implemented a drawer mechanism for the form pick feature, allowing users to view selected items without navigating away from the current page.
- Updated the `writePicks` function to repaint the UI correctly based on the current state of the picks.
- Created comprehensive tests for the drawer functionality, ensuring it opens correctly, displays the right items, and handles user interactions (incrementing, decrementing, and removing items).
- Introduced hydration tests to prevent React hydration errors by using `suppressHydrationWarning` on elements modified by the runtime.
- Enhanced the plugin query system to support scoped queries, allowing for dynamic updates based on user selections.
The generated `src/locales.ts` carries each locale's coverage, and adding the
stale-deploy error strings moved Japanese from 98% to 99%. `pnpm --filter
@zcmsorg/i18n test` refuses to pass while the checked-in catalogue disagrees
with the locale files, which is what caught it.
@zscontributor zscontributor changed the title fix(plugins): make a plugin UPGRADE land its schema, and uninstall survive a table that never existed fix(plugins): plugin upgrades land their schema; uninstall survives a missing table (also syncs develop with main) Jul 31, 2026
@zscontributor
zscontributor merged commit c174497 into develop Jul 31, 2026
23 of 25 checks passed
@zscontributor zscontributor changed the title fix(plugins): plugin upgrades land their schema; uninstall survives a missing table (also syncs develop with main) fix(api): reconcile plugin tables on upgrade and skip a missing one on uninstall Jul 31, 2026
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.

1 participant