feat(zenstack): upgrade to ZenStack v3.8.0 and remove Prisma#471
Open
therealbrad wants to merge 85 commits into
Open
feat(zenstack): upgrade to ZenStack v3.8.0 and remove Prisma#471therealbrad wants to merge 85 commits into
therealbrad wants to merge 85 commits into
Conversation
- Swap Prisma/ZenStack v2 deps for v3 (@zenstackhq/orm, plugin-policy, schema, server, tanstack-query, cli @3.8.0; add kysely + pg); remove @prisma/client, prisma, @zenstackhq/runtime, openapi, zenstack, and the unused @next-auth/prisma-adapter. - Scripts: prisma db push -> zenstack db push; drop v2 fix-zenstack-symlink; generate into ./zenstack with explicit --schema. - schema.zmodel: migrate to v3 via re-runnable codemod (scripts/migrations/zmodel-v2-to-v3.mjs): drop prisma generator + v2 hooks/zod/openapi plugins, add policy plugin; !auth() -> auth() == null (x146); remove @password; future() -> post-update/before(); exclude create from non-owned relation rules; drop field-level policy override arg. - Gitignore the generated /zenstack client (produced by postinstall).
- lib/zenstack.ts: ZenStackClient over a Kysely/pg pool with three layered views — rawClient (no plugins, @omit-readable), baseClient (+ side-effects), policyClient (+ @@allow/@@deny). getAuthDb(user) = policyClient.$setAuth(user). - lib/zenstack-plugins/sideEffectsPlugin.ts: no-op plugin seam; the v2 lib/prisma.ts $extends audit/ES/webhook/business-logic hooks get ported here in a follow-up (original preserved at the previous commit). - lib/prisma.ts, lib/prismaBase.ts, server/db.ts: thin re-export shims (prisma->baseClient, db->policyClient, prismaBase/server.db->rawClient) so the ~590 existing call sites resolve unchanged. - lib/auth/utils.ts: enhance(prisma,{user}) -> getAuthDb(user). - next.config.ts: serverExternalPackages -> v3 (orm, plugin-policy, kysely, pg). Verified live: layering + policy enforcement intact (anon=0, admin=full, non-admin filtered).
…ts to v3 - Remove the generated lib/hooks/ (121 files): v3 TanStack Query hooks are runtime (useClientQueries), not generated — consumers convert in a follow-up. - Codemod scripts/migrations/imports-prisma-models.mjs (idempotent): rewrite @prisma/client model-type and enum imports to ~/zenstack/models across 346 files. Enums import as values; model types as 'import type'. Prisma / PrismaClient left as residual @prisma/client imports for the namespace pass.
- lib/zenstack.ts: add DbClient (= raw client type) and TxClient
(= TransactionClientContract) aliases for the v2 PrismaClient /
Prisma.TransactionClient types.
- Codemod scripts/migrations/imports-prisma-namespace.mjs (idempotent) rewrote
123 files: Prisma.<Model>{WhereInput,GetPayload,Select,...} -> ~/zenstack/input;
Prisma.TransactionClient -> TxClient; Prisma.JsonNull/DbNull/AnyNull and
JsonValue/JsonObject/JsonArray (+ InputJsonValue->JsonValue) -> @zenstackhq/orm;
Prisma.SortOrder.asc/desc -> string literals; Prisma.Decimal -> decimal.js;
Prisma.PrismaClientKnownRequestError -> ORMError.
- add decimal.js direct dep.
73 files remain on @prisma/client for manual completion (flagged by the codemod):
new PrismaClient() in e2e/seed/test scripts (50), <Model>OrderByWithRelationInput,
plain Create/UpdateInput, raw SQL (Prisma.sql/empty), NullableJsonNullValueInput.
- Add lib/rawDbClient.ts: createRawDbClient() returns an independent raw ZenStackClient (own pool, no policy/side-effects) for seeds, scripts, and e2e tests that managed their own client lifecycle. - Codemod scripts/migrations/new-prismaclient.mjs: replace bare new PrismaClient() -> createRawDbClient() and drop the PrismaClient import across 49 files (e2e specs, seed scripts, integration tests). Verified the factory connects and exposes $disconnect. Remaining @prisma/client importers: 25 (OrderBy types, plain Create/UpdateInput, raw SQL composition, NullableJson, PrismaClient param types, multiTenant factory).
- Workflow-migrated the final 25 residual files (Prisma.* arg types ->
~/zenstack/input, raw SQL -> kysely sql via $qb, multi-tenant factory ->
ZenStackClient, OrderBy -> FindManyArgs['orderBy'], plain Create/UpdateInput
-> XArgs['data']); each fix independently verified.
- Fix searchQueryBuilder, EditResultModal, TestResultHistory, reviews/columns:
non-standard @prisma/client forms (runtime/library JsonValue, inline import()
type, dynamic enum import) -> @zenstackhq/orm / ~/zenstack/models.
- reviewDecisions: correct the ORMError construction to
new ORMError(ORMErrorReason.NOT_FOUND, ...) (was a broken {code:P2025} cast).
- Delete dead lib/prisma-middleware.ts (+ test): superseded by client
extensions, only self-referenced, and used a dynamic @prisma/client import.
App code is now fully off @prisma/client. Remaining: 21 test files (vi.mock /
dynamic-import of @prisma/client) for Phase 7; v3 error-shape rework
(lib/utils/errors.ts: ORMError has no .code) for Phase 6.
Resolved conflicts: keep v3 lib/prisma.ts shim (CDC's slimmed $extends becomes the Phase 3 plugin port); keep lib/hooks deleted (v3 runtime hooks); take main's logic for the RPC route + 8 routes/components/workers (codemods re-applied next); package.json = v3 deps + main's CDC scripts (apply-triggers, dcl-retention) + v0.40.6. Post-merge: re-run import codemods to migrate main's new CDC files + reverted files.
Re-ran the idempotent import codemods over main's CDC additions + the
conflict-reverted files, plus hand-fixes:
- lib/audit/{auditedTransaction,gucContext}: Prisma.TransactionIsolationLevel
-> TransactionIsolationLevel (@zenstackhq/orm).
- app/actions/searchProjectAuditLogUsers: raw Prisma.sql -> kysely sql via $qb.
- workers/testmoImportWorker: Prisma.*CreateManyInput -> *UncheckedCreateInput,
Prisma.TestmoImportJobUpdateInput -> TestmoImportJobUpdateArgs['data'].
- delete leftover generated lib/hooks/data-change-log.ts (v3 hooks are runtime;
DataChangeLog gets a v3 runtime hook).
- apply-triggers.ts already uses pg Client directly (ORM-agnostic, untouched).
App code fully off @prisma/client again. Verified live: client layer + policy
enforcement intact, DataChangeLog queryable via v3 client.
- Codemod scripts/migrations/hooks-v2-to-v3.mjs: convert v2 generated hooks
(useFindManyProjects, useCreateTags, ...) imported from ~/lib/hooks to the v3
runtime grouped client, inline:
useFindManyProjects(args, opts)
-> useClientQueries(schema).projects.useFindMany(args, opts)
Inline preserves v3 per-call generic inference (select/include narrowing) and
avoids placing a per-component client const. 278 files; longest-suffix model
matching disambiguates (TestRuns vs TestRunResults). 0 unparsed imports.
- app/providers.tsx: repoint the ZenStack hooks provider from the v2
runtime-v5/react Provider to v3 QuerySettingsProvider (@zenstackhq/tanstack-query
/react), preserving endpoint=/api/model + the operationIdFetcher (X-Operation-Id
header for CDC audit correlation).
Follow-up (Phase 6/7): 13 files manually invalidateQueries({queryKey:[...]}) with
v2-style keys that may differ under v3's grouped client.
- RPC route app/api/model/[...path]/route.ts: NextRequestHandler now takes
getClient + a required apiHandler (new RPCApiHandler({ schema })); getPrisma
returns the $setAuth'd client (deadlock-retry preserved).
- enhanceWithAudit -> thin alias for getAuthDb (policyClient.$setAuth); covers
the RPC route + copy-move/import/shared-steps bulk routes unchanged. GUC actor
injection moves to the Phase 3 side-effects plugin.
- auditedTransaction: auditedEnhancedTransaction inverts to
getAuthDb(user).$transaction(...) since v3 can't enhance() a tx client.
- Codemod scripts/migrations/enhance-to-setauth.mjs: enhance(client,{user:U}) ->
getAuthDb(U) across 9 entry points (stream/step-scan/export/preflight/metadata).
- change-password: read User.password with omit:{password:false} (v3 omits @omit
fields on all clients; other auth/2FA/share-link reads already use explicit select).
- NextAuth adapter already takes DbClient + PrismaAdapter (v3 is Prisma-compatible).
- Fix codemod import-injection bug: hooks-v2-to-v3 + new-prismaclient injected
imports after the FIRST LINE of a multi-line import, splitting it. Now prepend
before the first import. Re-ran hooks codemod (278 files) cleanly; repaired the
split in reindexElasticsearch; hooks codemod now skips test files.
- Reset 12 test files the hooks codemod broke (it rewrote v2 hook names used as
vi.mock object keys) — test mock rework is Phase 7.
- hooks/useRepositoryCasesWithFilteredFields: type positions use ClientHooks
indexing (typeof <call-expr> is invalid).
- lib/utils/errors.ts: rework for v3 ORMError (no .code) — dbErrorCode SQLSTATE
(23505/23503) + reason NOT_FOUND, plus text-match for client {info:{message}};
isAlreadyPendingError matches the partial-index text.
- 4 SCIM routes: Prisma.PrismaClientKnownRequestError check -> isUniqueConstraintError.
- 6 seed/scripts: restore the lost createRawDbClient import (also fixes the
non-module 'prisma' redeclare).
…cimal, @omit - lib/zenstack.ts: TxClient = Omit<typeof rawClient, tx-unsupported> instead of TransactionClientContract<typeof schema> — the latter widened tx query results to 'any', causing 73 implicit-any (TS7006) errors across transaction routes. This single fix cleared all 73. - RPC route: type the handler result Response|null (baseHandler returns Response, not NextResponse) which cleared the 18 'response possibly null' errors; isolationLevel '"Serializable"' -> TransactionIsolationLevel.Serializable. - lib/llm/services/llm-manager: wrap cost numbers in new Decimal() (v3 Decimal fields reject plain number on input). - app/api/share/[shareKey]: read passwordHash via omit:{passwordHash:false}. Non-test type errors: 198 -> ~73 real (+18 known next-intl t() local-tsc false-positives that clear on a Next build).
…y allows isolationLevel)
v3 $transaction options accept only { isolationLevel } (no Prisma maxWait/timeout)
— passing them broke overload resolution (TS2769). Stripped the option objects
across testmo imports (automationImports/issueImports/configurationImports/
testmoImportWorker) and the hardcoded worker/service calls; auditedTransaction
now forwards only isolationLevel. Cleared 17 of 19 TS2769.
Non-test type errors: ~54 real remaining (+18 known i18n false-positives).
…Hash:false} on both fetches
…orCode - LLM integration cost fields -> new Decimal() (Add/Edit/llm-manager) - JSON fields with possibly-undefined values cast as JsonValue (credentials, Issue.data in jira/create + create-issue trackerFields) - .code===Pxxxx checks -> isUniqueConstraintError/isNotFoundError/dbErrorCode (admin/tags/create, reviews/decide, webhooks/coalescing)
…t, share audit - transformSteps accepts null; AddResultModal notes/evidence, SessionResultForm resultData, issueImports settings, seed AppConfig value, webhooks-seed payload, test-credentials settings -> JsonValue casts - verifyEmail emailVerified -> Date (not toISOString string) - removed dead lazy @zenstackhq/runtime enhance() in SyncService - getScimTokenWithSecret() opts @omit secret back in for probe decryption - share-link creation audit takes hasPassword bool (hash no longer sent to client) - webhook-config JSON path filter -> string (v3 shape)
…Table/TestRunDisplay props - AddCase/[caseId] sharedStepGroup useFindMany: let v3 infer (drop manual type) - admin/users + runs/page: cast v3 query results to ExtendedUser[]/TestRunsWithDetails[]
…rt, TS2589, find callbacks
- filterOrphanedFieldValues unconstrained generic (v3 result types too complex
for the old constraint); loosen post-fetch filter param
- sso nested samlConfig upsert: v3 requires where -> { providerId }
- EditResultModal/[caseId] find(): drop stale explicit element annotations
- TS2589 deep-instantiation on testmoImportJob/user .update(): args cast
(data already typed at declaration)
Real (non-test) type-check is now GREEN. Remaining 21 are next-intl t() local-tsc
false-positives (clear on a Next build).
Replaces the no-op stub with the full write-side-effects port from main's lib/prisma.ts $extends, adapted to the v3 plugin API: - onQuery: arg-rewriting business logic (TestRuns auto-completedAt) - beforeEntityMutation: app.audit_context GUC SET LOCAL inside the mutation's transaction so CDC triggers attribute the actor (skipped when already inside an auditedTransaction); before-image capture for update/delete diffs - afterEntityMutation (runAfterMutationWithinTransaction: true): Elasticsearch sync (fire-and-forget), in-tx webhook emits (case/run/session/issue/JUnit/ testRunResult/sessionResult), the draft-case run-exclusion business logic, and the remaining semantic audit (SSO/system-config) + API-token cache evict The CDC refactor emptied AUDITED_CONFIG_MODELS and SEMANTIC_ACCESS_AUDIT_MODELS (triggers are sole source for row audit), so the generic config/access audit hooks are intentionally not ported. Type-check + production build green. Runtime parity (GUC attribution, webhook atomicity, bulk handling) is validated in the Phase 7 E2E pass.
v3 grouped data hooks call React context internally and crash component renders without the app providers. A default inert stub (per-file mocks still override) clears 359 unit failures (709->350) with no regressions.
…udit
Routes use getAuthDb(user) (or enhanceWithAudit alias) instead of
enhance(prisma,{user}). Updated 9 test files: 6 getAuthDb route tests +
audit-log export + import/route (enhanceWithAudit) + auth/utils (getEnhancedDb
delegates to getAuthDb; getUserWithRole reads baseClient).
…eClientQueries
Replace vi.mock("~/lib/hooks") + imports with vi.hoisted mock fns wired into a
per-file useClientQueries mock (component reads via the v3 grouped client).
…ntQueries Hoist the two driven read hooks; inline inert stubs for the other 7 model ops the component calls. Proves the multi-hook component-test pattern.
… merge) Codemod now matches lib/hooks and lib/hooks/<subpath> factories together and merges all into one useClientQueries mock per file (avoids the double-mock that silently dropped hooks). Applied to 68 test files. Unit failures 709 -> 60.
…a codes) v3 has no Prisma and no P-codes. Construct ORMError directly with the Postgres SQLSTATE in dbErrorCode (23505 unique / 23503 FK) and the NOT_FOUND reason, matching what the helpers actually detect; drop the fake makePrismaError.
…aw-SQL
The action issues its filtered query via Kysely sql``.execute(prisma.$qb); mock
$qb.getExecutor() with passthrough transform/compile (to inspect the raw node)
and executeQuery returning { rows }.
Same prisma.$qb.getExecutor() mock as scimConflictLogActions; adds a boundValues helper that walks the Kysely raw node's ValueNodes (replacing v2 Prisma Sql.values) to assert the project-scoped bound parameters.
…on fixtures) - probe.test mocks getScimTokenWithSecret (the fn probe.ts now calls) - scimTokenActions + 4 scim/v2 route tests build a real v3 ORMError unique violation (dbErrorCode 23505) instead of a malformed Prisma P2002 object
…ard message - coalescing: v3 unique-violation (23505) / FK (23503) ORMErrors, not Prisma codes - iterationEvents + events: tx-guard assertions match the v3 'requires a TxClient' message (was Prisma.TransactionClient/transaction)
…v3 rewrite - matrixCellCount: $qb executor wraps rows for the two preflight COUNT queries - enhanceWithAudit: rewritten to assert the v3 reality (thin getAuthDb alias), dropping the obsolete v2 enhance(prismaBase)-vs-hooked-client invariant
…d types @testplanit/mcp-server was the last package using Prisma. It types its ZenStack RPC query args (where/include/select) with schema-specific types that previously came from the testplanit-generated Prisma client. - Source those types from testplanit's ZenStack-generated types via a `@db/*` tsconfig path-alias (+ @zenstackhq/orm for JsonValue); drop @prisma/client. - Convert all 40 source files: `Prisma.<X>` -> the ZenStack `<X>` types; derive the few v3 doesn't export top-level (WhereUnique / OrderByWithRelation / CountOutputType) as local aliases. - Apply the RepositoryCases tags/issues -> caseTags/caseIssues join-model change across the case/issue tools: read includes, where filters, create/update writes (nested join-model create + deleteMany-inside-update, preserving the soft-delete invariant), and row mappers. Sessions/TestRuns stay implicit m2m. Public tool output shapes (tags, issues, counts) are unchanged. - Update the 8 affected test files to assert the new shapes. Verified: typecheck 0 errors, 653/653 tests pass, tsup build succeeds, zero @prisma/client references.
Cosmetic follow-up to the Prisma removal — the shim files and the `prisma`
client variable kept the legacy name. Rename them to ZenStack-appropriate names
(no behavior change):
lib/prisma.ts -> lib/db.ts ; export `prisma`(baseClient) -> `baseDb`
(export `db` = policyClient unchanged)
lib/prismaBase.ts -> lib/rawDb.ts ; export `prisma`(rawClient) -> `rawDb`
lib/multiTenantPrisma.ts -> lib/multiTenantDb.ts
getTenantPrismaClient -> getTenantDbClient
getPrismaClientForJob -> getDbClientForJob
lib/prisma.config-audit.test.ts -> lib/config-audit.test.ts
server/db.ts unchanged (already db-named)
All ~450 call sites updated (import paths + the `prisma` identifier -> baseDb /
rawDb, scope-aware per source). vi.mock paths + factory keys updated to match.
Verified: type-check clean of rename errors, Vitest 9724 pass, prod build OK.
Final cosmetic pass of the Prisma removal — eradicate the `prisma` name from all remaining identifiers (test mocks, heritage type aliases, helper fns, and local client vars). Token-level rename Prisma->Db / prisma->db, e.g.: mockPrisma -> mockDb, prismaMock -> dbMock, buildPrismaMock -> buildDbMock, fakePrisma -> fakeDb, prismaClient -> dbClient, createCustomPrismaAdapter -> createCustomDbAdapter, scimFilterToPrismaWhere -> scimFilterToDbWhere, PrismaLike -> DbLike, PrismaClientType -> DbClientType, RawPrismaClient -> RawDbClient, the local `const prisma = createRawDbClient()` -> `db`, etc. Intentionally left (not identifiers): standalone `Prisma` in comment prose (describes the ORM/historical context), the real `_prisma_migrations` Postgres table name, the `feedback_prisma_helper_live_db_test` doc slug, and the `PrismaClientValidationError` proper-noun comment reference. No behavior change. Verified: type-check (no new errors), Vitest 9724 pass, prod build OK.
scripts/fix-zenstack-symlink.js is a ZenStack v2 artifact: it resolves @zenstackhq/runtime (absent under v3) and copies v2 runtime files (.zenstack symlink, enhance.js, policy.js) that v3 doesn't produce — under v3 it errors immediately and exits 0 (no-op). It was invoked 4x in the Dockerfile and was the source of the "Cannot find module '@zenstackhq/runtime/package.json'" build warnings. Remove the script + its Dockerfile invocations. Verified: production + workers Docker images build clean (bake exit 0).
All three were test fragility, not app bugs — no app code changed.
- markdown-paste-and-import :105/:213: read-after-write race. saveChanges()
returned when the Edit button reappeared (isSubmitting flips it back the
instant Save is clicked), but the field-value POST was still in flight, so the
API read-back sometimes beat the create. New saveChangesAndWaitForFieldValue()
waits for the POST /api/model/caseFieldValues/{create,update} response (the
real "persisted" signal) before reading back; removed the fixed sleeps.
- authoring-step-mentions :14: the undeclared-parameter warning ribbon only
fires on a parameterMention NODE, but @-autocomplete only offers DECLARED
params, so typing "@notDeclared" + Escape left plain text that never became a
mention. The step now pastes an undeclared parameterMention node (reproduces
the real "parameter removed from the declared set" state the ribbon guards).
- drag-drop-modifier-aware :276 (+ 6 dependents): react-dnd's HTML5Backend only
reacts to native HTML5 drag DOM events; the helper drove the drag with
mouse.down/move/up, which never reach the backend, so the drop handler (and
the cursor-anchored popover) never fired. New nativeDragDrop.ts helper
synthesizes genuine HTML5 drag events (one threaded DataTransfer, modifier
keys on dragover/drop, a frame flush for the rAF-scheduled hover); the
modifier/badge tests use the hold-drag variant so intent commits before drop.
Verified green: markdown-paste 3x consecutive; drag-drop 8/8 (no retries);
all 3 specs together 12/12.
All three were pre-existing test-side timing races (no app behavior changed), surfaced as hard failures in a full run where the retry didn't save them. - sorting :341 (Verify Name Column Sort Order - Ascending): getColumnValues read the table during the post-sort refetch and caught the 10 DataTable skeleton rows (no data-row-id). Wait for exactly 3 real rows via an auto-retrying toHaveCount before reading the column (mirrors the sibling sorting tests). - session-duplication :108: AddSessionModal seeds its name field from a one-shot reset() effect that fires after first render and rewrites the name back to "<original> - Duplicate"; the test's clear()+fill(newName) was being clobbered, so the session got created under the wrong name and the lookup returned null. Fill the name under an expect(...).toPass() retry loop (the ref-guarded reset fires once, so the re-fill wins) + poll the read-back. - test-run-case-execution :366: Pass & Next only escalates to the Add Result modal once the templateResultAssignment findMany resolves (gated on case/ template load); clicking before it resolved took the non-escalation path. Wait for that response before clicking Pass. Verified: session-duplication 12/12 + 3/3 stress; test-run-case-execution 9/9 + 6/6 stress; sorting 2/2 — all zero retries.
The Descending sibling of the Ascending sort test had the same getColumnValues skeleton-row race (read 10 placeholders during the post-sort refetch). Add the same auto-retrying toHaveCount(3) on data-row-id rows before reading. Verified: full sorting.spec green (33 passed), :341 + :390 both pass.
…tion Deploys ran `zenstack db push --accept-data-loss` on every container boot, which would silently drop data on any lossy schema change — the v2->v3 implicit->explicit m2m conversion (case<->tag / case<->issue) being the immediate case (94k rows). Switches the project to ZenStack/Prisma migrations: - migrations/20260625193632_init: baseline = the schema as of the last db-push release (v2-equivalent, incl. the implicit _RepositoryCasesToTags / _IssueToRepositoryCases join tables). - migrations/20260625193819_explicit_case_tag_issue_join: converts the implicit m2m to the explicit RepositoryCaseTag / RepositoryCaseIssue join models, DATA-PRESERVING (create -> copy -> drop in one transaction). The generated diff dropped first; it was reordered and given INSERT...SELECT copies (with the reversed Issue/RepositoryCases column mapping handled). - docker-entrypoint.sh + all three docker-compose db-init services: `db push --accept-data-loss` -> `migrate deploy` (non-destructive). - Dockerfile: ship the migrations/ dir so the entrypoint can apply it. - package.json: `generate`/`generate:dev` are now codegen-only (they no longer db push against whatever DATABASE_URL points at); added db:migrate(:dev), db:deploy, db:status, and a db:push escape hatch for throwaway local use. - migrations/README.md: the one-time baseline procedure for existing (pre-migration) databases — `migrate resolve --applied 20260625193632_init` then `migrate deploy`. Validated end-to-end on a scratch PG15: fresh install (init+change -> v3 structure) and the existing-DB path (db-push origin -> baseline resolve -> deploy) both preserve the seeded m2m links with the correct column mapping and drop the implicit tables.
Brings #467-472 (audit-trigger self-heal + CLI guard, seed default-preservation, dependency bumps incl. react-day-picker 10 / react-resizable-panels 4 / next-intl 4.13 / @zxcvbn-ts 4) onto the ZenStack v3 branch. Conflict resolutions kept v3 patterns while absorbing main's fixes: - package.json / mcp-server: main's dep bumps applied; prisma + zenstack-v2 deps stay removed; zenstack v3 deps + migrate scripts kept. - scripts/apply-triggers.ts: took main's exportable self-heal applier (advisory lock, robust path resolution, CLI guard), retargeted prisma/ -> db/. - db/seed.ts, db/seedPromptConfig.ts: took main's admin-default-preservation logic on the v3 db client (prisma. -> db.). Validated: prod build passes; source type-checks clean (remaining tsc errors are the pre-existing v3 test-file baseline); lockfile consistent (frozen-lockfile OK).
react-resizable-panels v4 renders the group element's data-testid from its `id` prop and ignores a passed `data-testid`. The v4-compat wrapper spread the prop through but v4 overrode it with an auto-generated id, so every group testid (e.g. data-testid="repository-layout") rendered as a random id — the repository page, AddCase, and the project-overview panel group all became unqueryable, failing their E2E specs (the merge that bumped resizable-panels to v4 surfaced this). Map an explicit data-testid onto the v4 `id` so the rendered testid matches. Verified: the group now renders data-testid="repository-layout".
v4 renamed the internal DOM hooks the overview/steps specs queried: - [data-panel-group] -> [data-group] - [data-panel-resize-handle-id] -> [role="separator"] - [data-panel="<id>"] -> getByTestId(id) (v4 derives the element data-testid from the panel id; data-panel is now the boolean "true") Fixes the overview panel-group/handle assertions and the shared-steps / steps-display handle locators. (The separate left-panel collapse-behavior assertion is still under review.)
…group) Extends 31af532 to ResizablePanel. v4 derives the panel element's data-testid from its `id` and ignores a passed `data-testid`, so panels that set both an `id` (identity) and a different `data-testid` (test hook) — e.g. the repository left panel: id="repository-left" + data-testid="repository-left-panel" — rendered the wrong testid, breaking the folder-tree specs (folder-creation, tree-navigation, view-switching, folder-delete/edit) that query the panel. Prefer data-testid for the v4 id (the rendered testid is what callers query; the id is internal layout state). Verified at render level: group, both panel forms resolve the expected testids.
react-resizable-panels v4 renders each panel with overflow:visible (v3 clipped),
so a panel collapsed to width 0 still showed its content — the min-content width
of flex children spills out of the 0-width panel. Restore v3 clipping via a
globals.css rule ([data-panel]{overflow:hidden!important}; the library ignores an
inline style override). The resize handle sits outside the panel and menus use
portals, so only in-panel content is affected (in-panel scrolling stays on the
inner wrapper).
Also harden the overview collapse test: a clipped element keeps its layout
bounding box, so Playwright's toBeHidden on the content text is unreliable;
assert the collapse structurally via the panel width (0 collapsed, >0 expanded).
Verified: project-overview-dashboard.spec.ts 10/10 green.
Fixes CI 'Check formatting' (pnpm format:check). The v3 migration's bulk codemods + the merged prettier-plugin-tailwindcss 0.8.0 bump left ~367 files unformatted (local precommit was run scoped because full eslint OOMs locally). Pure prettier --write output — line-wrapping to print width + Tailwind class re-sorting, no logic changes.
CI 'Run linter' (eslint . && tsc --noEmit) failed on 15 @typescript-eslint/no-unused-vars errors — leftovers from the v3 codemod and the merge's $transaction-options removal: unused db / ORMError / TxClient imports, the dead SharedStepGroupWithItems + SharedStepItemDetail (+ now-unused JsonValue) interfaces, and dead TRANSACTION_* timeout consts (v3 $transaction only takes isolationLevel). Removed them.
CI 'Run linter' also runs tsc --noEmit, which had 37 errors — v3-migration type
debt in test files (masked because vitest uses esbuild, not tsc) plus one source
file. All fixed with minimal, behavior-preserving changes:
- milestoneActions/matrixCellCount tests: v3's overloaded $transaction/$qb mocks
weren't callable under the inferred type — annotated/cast the mock callbacks.
- server/auth-adapter.ts: TS2589 deep-instantiation on db.user.update — cast the
call target to break inference depth (external Promise<AdapterUser> preserved).
- ORMError .code -> .dberrorCode/'23505' (v3 ORMError has no .code; matches lib/utils/errors.ts).
- scim tokens test: omit:{secret:false} to opt the @omit secret back in (mirrors source).
- scim groups/users + share-links + data-model-foundation: cast raw-JSON WhereInput,
fix stale hasPassword fixture, enhance() -> getAuthDb (v3 equivalent).
pnpm type-check exits 0; eslint clean on changed files; affected vitest files pass.
prettier format:check fix for iterationGenerationWorker.ts (the unused TRANSACTION_* const removal in 8f5acce left a double blank line).
…589 cast) The E2 source-shape sanity test counted /\.user\.update\(/ which no longer matched updateUser after it cast the method to break a TS2589 deep-instantiation ((db.user.update as ...)(...)). Match the identifier db.user.update\b instead — still exactly two, still excludes updateMany, intent (catch an accidental 3rd user mutation) preserved.
…dle crash The rich-text editor configured ImageWithResize as inline:true while the custom Image extension sets group:"block" and all stored content places images at block level. That contradiction (inline node at block level) was tolerated by @tiptap 3.26 but @tiptap 3.27 (bumped in the main merge) validates content strictly and threw 'Called contentMatchAt on a node with invalid content', crashing the always-mounted DragHandle (ContentItemMenu) on ANY doc containing an image — shared steps, documentation, rich case fields. Found via the tpiv1 prod-data smoke test (shared-steps group with an image); the E2E seed has no block images so it slipped through. Set inline:false so block images validate. Adds blockImageSchema.test.ts (getSchema + Node.check) reproducing the crash and guarding against reintroducing inline:true.
Reflects the v3 dependency shift: Prisma removed (ORM now built on Kysely + node-postgres), added Kysely / node-postgres (pg) / decimal.js notices, dropped the removed Atlaskit editor-jira-transformer entry, and corrected license fields.
Production deploys (*.testplanit.com) run exclusively on arm64, so building
linux/amd64 images was wasted CPU. Removes the build-amd64 and docker-manual-amd64
jobs and the now-unnecessary multi-arch manifest-merge jobs; the arm64 build now
publishes the final tags directly (${version}, ${version}-workers,
workers-sha-${sha}) and moves :latest/:latest-workers only when the tag is the
newest semver. Smoke-test and BASE_DOMAIN wildcard behavior are unchanged.
Clicking the Reports tab after running a custom report bounced back to Report Builder (URL flipped to tab=builder&reportType=test-execution with a stray ?snapshotId). Root cause: the Reports tab's first pre-built report is automation-candidates, so the click mounts AutomationCandidatesReportPreset, whose 'default to newest snapshot' effect ran on mount and called router.replace(new URLSearchParams(searchParams) + snapshotId). That searchParams snapshot is stale during the in-flight tab navigation, so the replace overwrote the tab/reportType change — a classic multi-writer URL race. Make the automatic default update local state only; never write the URL from the auto-default. Explicit snapshot selection from the dropdown still goes through setSelectedSnapshotId and writes ?snapshotId (an un-pinned auto-default resolves to 'latest', which is the newest snapshot it selects anyway), so share fidelity is unchanged. Pre-existing on main; surfaced by the v3 prod-data smoke test.
Running a custom report then clicking the Reports tab bounced straight back to
Report Builder. Reproduced live and traced to the report-type Select: while the
tab switches, the Select momentarily emits onValueChange("") (its current value
is briefly not among the freshly-rendered options), and handleReportTypeChange
coerced that empty value to the "test-execution" fallback — reverting the tab,
reportType, and URL back to the builder. Compounding it, the auto-run effect
re-ran the previous custom report and runReport rewrote the URL, and the tab/
reportType-from-URL sync effects reverted optimistic state off the stale URL.
Fixes:
- handleReportTypeChange ignores empty / unknown report types instead of falling
back to a default (the actual bounce trigger).
- runReport gains a persistUrl option; only an explicit Run persists selections
to the URL, so an auto-run can no longer rewrite (and clobber) the URL.
- handleTabChange switches reportType together with the tab (resolveTabChange) so
the Reports tab shows a valid pre-built report, with pendingTab/pendingReportType
guards so the URL-sync effects don't revert the switch mid-navigation.
Pure helpers (resolveTabChange / resolveSyncedActiveTab / resolveSyncedReportType)
are unit-tested. Verified end-to-end in a browser: Builder->Reports stays, the
round-trip is stable, and the report dropdown still selects correctly. Pre-existing
on main; surfaced by the v3 prod-data smoke test.
| // its URL write would clobber the new tab/reportType (the bounce). The | ||
| // selections are already in the URL from the explicit run, so auto-runs | ||
| // don't need to rewrite them. | ||
| if (updateUrl && !currentReport?.isPreBuilt) { |
Adds a Playwright E2E that reproduces the reported flow — build + run a custom report, click the Reports tab — and asserts the Reports tab stays active (URL tab=reports, reportType no longer test-execution) instead of bouncing back to Report Builder, plus a stable Reports<->Builder round-trip. Adds data-testid 'reports-tab' / 'report-builder-tab' to the tab triggers for robust selectors.
stream-json v3 restructured the package: the old capital-A "stream-json/Assembler" entry no longer exists and fails to resolve with MODULE_NOT_FOUND at runtime on case-sensitive filesystems (Linux/Docker) — breaking the testmo-import-worker. It stayed latent on macOS (case-insensitive FS). Point the import at the new "stream-json/assembler.js" subpath and update the ambient declaration to match. Also deletes the dead TestmoExportAnalyzer.original.ts backup (nothing imported it; a leftover from the initial public release) which still carried the broken import — so the legacy capital-A type declaration is no longer needed.
12 tasks
… image The build stage's 'next build' could not resolve ~/zenstack/models when building the image from a fresh clone: testplanit/zenstack/ is gitignored (generated), so it isn't in the build context, and the base stage's 'pnpm zenstack generate' was missing '--schema schema.zmodel -o zenstack' — so it wrote the v3 client to the default output dir instead of testplanit/zenstack. Install runs with --ignore-scripts, so the postinstall generate never fires either. Match package.json's generate command so the client lands at ~/zenstack/*.
…ettier 3.9 Adopt the previously-deferred majors except ESLint 10, plus minor bumps. Two adjustments keep the suite/precommit green: - Revert eslint + @eslint/js to ^9.39.4: eslint-plugin-react@7.37.5 (latest) peers eslint up to ^9.7 and crashes on ESLint 10's removed context.getFilename() during React-version auto-detection. ESLint 10 stays deferred until eslint-plugin-react supports it. - vitest.config.mts: set SKIP_ENV_VALIDATION for the test env. Under TS 6 the transform no longer elides a transitive env.js import, so @t3-oss/env validated DATABASE_URL/NEXTAUTH_URL at collection and 3 (skipped) suites failed to load. Unit tests must not require real runtime env. TypeScript 6 type-check clean (0 errors); unit suite 9742 pass / 0 fail.
Prettier 3.8 -> 3.9 adjusts whitespace/line-wrapping (e.g. collapses short union types onto a single line). Mechanical reformat only; no behavior change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Upgrades the data layer from ZenStack v2.22.3 (on Prisma) to ZenStack v3.8.0 (its own Kysely-based, Prisma-API-compatible ORM) and removes Prisma from the repository entirely — both functionally and by name.
Highlights:
@@allow/@@denyrules (incl. the ~198 nested?[…]rules) are enforced at runtime by the v3PolicyPlugin— this was the historical blocker and is now working in v3.8.0. No security regression: the previously-skipped ACL E2E tests pass with policy enforcement on.@prisma/client/prismaruntime dependency;prisma generateremoved from the build; production Docker images build clean against ZenStack only. The client layer lives inlib/zenstack.ts(rawClient/baseClient/policyClient,getAuthDb/getEnhancedDb), with thin shims at the old import paths. Prisma-named identifiers were renamed todbnaming throughout (mocks included); only standalonePrisma-the-tool prose in comments remains.$extendshooks → v3 plugin. The ~30-model audit + Elasticsearch + webhook side-effects from the oldlib/prisma.tswere ported to a v3sideEffectsPlugin(onQueryfor business logic/audit/bulk ops,onEntityMutationfor post-commit ES sync and in-tx webhooks).useFindManyX) converted to the v3 grouped client (client.x.useFindMany); custom hooks re-pointed.@auth/prisma-adapter).$transactionoptions,Json?null handling, error-shape helpers, raw-SQL via the policy client, nested-orderBy, 63-char alias limit).Related Issue
Closes #61 (relates to #60, #62, #63, #64, #66)
Type of Change
How Has This Been Tested?
Full Vitest suite green. Production-build E2E (
pnpm build && E2E_PROD=on pnpm test:e2e) runs at ~1290 passing — zero regression versus the pre-upgrade baseline (the few residual failures are pre-existing parallel-load flaky/race tests, not related to this change). All previously-skipped ACL tests pass with policies enforced.Test Configuration:
Checklist
Additional Notes
Intended to ship first as a beta (
v1.0.0-beta.0image tag) so it can be validated without disturbing the stable:latest. The release pipeline already supports this: a prerelease tag does not match the:latestsemver guard inrelease.yml, so the beta images publish under their own tag only. Merging this PR tomainis what would flip:latestto v3 — so the beta tag is pushed independently while this PR stays open for review.Note on versioning at merge time: to cut a 1.0.0 stable release, the squash-merge title needs to signal a breaking change (e.g.
feat(zenstack)!: …or aBREAKING CHANGE:footer); a plainfeat:would bump a minor.