Skip to content

fix: a few small fixes#2487

Merged
ymc9 merged 8 commits intozenstackhq:devfrom
elliots:multiple-files-fixes
Mar 23, 2026
Merged

fix: a few small fixes#2487
ymc9 merged 8 commits intozenstackhq:devfrom
elliots:multiple-files-fixes

Conversation

@elliots
Copy link
Copy Markdown
Contributor

@elliots elliots commented Mar 15, 2026

I'm importing schemas across packages, came across a few issues as I went. Happy to split it out if you prefer.

  • fix: load plugins with jiti: I wasnt able to load a plugin from a workspace package
  • createOpposite option on @ relation: im importing schema files across packages, I'd like a one way import.
  • fix: resolve plugin paths against the schema file where the plugin is: previously it was loading relative to the main schema, not the one where the plugin is actually defined.
  • fix: add migrate diff command: was missing, was it meant to be?

Summary by CodeRabbit

  • New Features

    • Plugins can now be resolved using bare package specifiers (e.g., workspace packages) in addition to file paths.
    • Plugin paths are now correctly resolved relative to the schema file where they are declared.
  • Tests

    • Added test coverage for bare package specifier plugin loading and relative plugin path resolution.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 15, 2026

📝 Walkthrough

Walkthrough

This PR enhances plugin module resolution by adding jiti-based fallback imports for bare package specifiers and updating plugin path resolution to derive from the plugin's declared source location instead of the entry schema file directory. New test coverage validates both scenarios.

Changes

Cohort / File(s) Summary
Plugin Resolution Logic
packages/cli/src/actions/action-utils.ts, packages/cli/src/actions/check.ts, packages/cli/src/actions/generate.ts
Adds jiti-based import fallback for bare package specifiers; updates plugin module resolution to load from plugin's declared source directory via $cstNode?.parent?.element.$document?.uri?.fsPath with fallback to schemaFile; changes error handling to rethrow instead of log to console.
Import Ordering
packages/cli/src/index.ts
Reorders dotenv/config import to appear after other third-party imports.
Test Coverage
packages/cli/test/generate.test.ts
Adds formatDocument import and exports getDefaultPrelude from utils; introduces two new tests validating plugin loading via bare package specifier (jiti) and relative path resolution relative to schema declaration location.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A plugin resolver's tale, I hop with glee,
Bare packages now load via jiti with ease,
Paths resolve where declared, not where schemas dwell,
New tests ensure the magic works so well!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix: a few small fixes' is generic and vague, using non-descriptive language that doesn't convey the specific changes made (jiti plugin loading, plugin path resolution, import reordering, and new tests). Use a more specific title that highlights the main fixes, such as 'fix: plugin loading via jiti and relative path resolution' or similar to clearly indicate what changes are being made.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/actions/migrate.ts (1)

50-58: ⚠️ Potential issue | 🟠 Major

Move schema generation into runDiff or make it conditional for non-schema-based diff modes.

The run() function unconditionally calls requireDataSourceUrl() and generateTempPrismaSchema() at lines 52-55 before the switch statement. This will fail for valid diff modes like URL-to-URL or migrations-to-migrations that don't require the current project's schema. The temp schema should only be generated when fromSchemaDatamodel or toSchemaDatamodel is set to a schema-based mode, not for datasource-to-datasource comparisons.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/actions/migrate.ts` around lines 50 - 58, The current run()
unconditionally calls requireDataSourceUrl(schemaFile) and
generateTempPrismaSchema(schemaFile, prismaSchemaDir), which breaks URL-to-URL
and migrations-only diff modes; move schema validation/generation into runDiff
or guard it with checks on the diff modes so a temp schema is only created when
a schema-based side is required (e.g., when fromSchemaDatamodel or
toSchemaDatamodel equals a schema mode). Concretely, remove or defer the
requireDataSourceUrl/schema generation from run(), and in runDiff (or the call
site that inspects diff mode) call requireDataSourceUrl and
generateTempPrismaSchema only when fromSchemaDatamodel === 'schema' or
toSchemaDatamodel === 'schema' (or whichever enum/value you use for schema-based
modes), preserving the existing prismaSchemaDir logic when constructing the temp
schema.
🧹 Nitpick comments (2)
packages/cli/test/migrate.test.ts (1)

73-76: Strengthen this test beyond “no throw”.

This currently behaves as a smoke test only. If runCli exposes stdout/stderr, assert the --script output contains expected SQL diff content to catch functional regressions in migrate diff.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/test/migrate.test.ts` around lines 73 - 76, The test only checks
that migrate diff doesn't throw; enhance it by capturing runCli's stdout/stderr
and asserting the --script output contains expected SQL diff text (e.g., "CREATE
TABLE", table/column names from the provided model, or specific ALTER/CREATE
statements) to validate the generated SQL; update the test case in
migrate.test.ts (the "supports migrate diff with --from-empty and
--to-schema-datamodel" it block) to call runCli in a way that returns
stdout/stderr and add assertions against that output rather than relying solely
on no exception.
packages/cli/src/actions/generate.ts (1)

189-193: Consider extracting shared plugin source-path resolution logic.

This logic now exists in both generate.ts and check.ts. Moving it to action-utils.ts would reduce drift risk and keep plugin resolution behavior centralized.

Refactor sketch
+// action-utils.ts
+export function getPluginSourcePath(plugin: { $cstNode?: any }, schemaFile: string) {
+    return plugin.$cstNode?.parent?.element.$document?.uri?.fsPath ?? schemaFile;
+}
-const pluginSourcePath =
-    plugin.$cstNode?.parent?.element.$document?.uri?.fsPath ?? schemaFile;
+const pluginSourcePath = getPluginSourcePath(plugin, schemaFile);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/actions/generate.ts` around lines 189 - 193, Extract the
repeated plugin source-path resolution into a shared helper in action-utils.ts
(e.g., resolvePluginSourceDir or getPluginSourceDir) that accepts a plugin node
and a fallback schemaFile and returns the directory path (using
plugin.$cstNode?.parent?.element.$document?.uri?.fsPath ?? schemaFile then
path.dirname(...)); replace the inlined logic in generate.ts (where cliPlugin is
loaded with loadPluginModule(provider, path.dirname(pluginSourcePath))) and the
similar code in check.ts to call the new helper and pass its result into
loadPluginModule so both places share the same resolution behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/actions/migrate.ts`:
- Around line 162-205: runDiff constructs a shell command by joining parts and
calls execPrisma with a single string, which allows unescaped values
(options.fromUrl, options.toUrl, options.shadowDatabaseUrl, options.extraArgs)
to be interpreted by the shell; change execPrisma usage so arguments are passed
safely (either by making execPrisma accept an argv array and call
child_process.spawn/execFile without a shell, or by properly shell-escaping each
part before joining) and update runDiff to pass the array of parts (or escaped
parts) instead of parts.join(' '); reference runDiff and execPrisma (and the
exec-utils helper that currently uses execSync) when making the change.

In `@packages/sdk/src/prisma/prisma-schema-generator.ts`:
- Around line 556-590: The FK generation uses ID fields from oppositeModel and
only the first ID, causing wrong references and dropping composite keys; update
the logic in prisma-schema-generator to call getIdFields(decl) (not
oppositeModel), iterate all returned id field names to (a) create corresponding
ref FK fields on prismaOppositeModel (using ModelFieldType and allFields to
mirror types) and (b) build the `@relation` 'fields' and 'references' arrays by
mapping each idFieldName to a PrismaAttributeArgValue('FieldReference', new
PrismaFieldReference(...)) — use fieldName+CapitalizedId for the FK names and
idFieldName for references so addField(fieldName, new
ModelFieldType(decl.name,...), [new PrismaFieldAttribute('@relation', [...])])
contains arrays for all id fields rather than a single idFields[0].
- Around line 542-555: In generateMissingOppositeRelations(), the opposite-field
lookup currently matches only by target model name (using
getAllFields(...).filter(f => f.type.reference?.ref?.name === decl.name));
change this to extract the `@relation` name from the field (e.g., read relation
name from the field's relation metadata—refer to symbols fieldRel / thisRelation
used by the validator) and match opposite fields by relation identity (relation
name) in addition to the target model so multiple relations to the same model
are distinguished; update the filter to compare the extracted relation name for
both the current field and each candidate opposite field. Also replace the
silent continue when a generated fieldName collides with an existing
prismaOppositeModel field (the prismaOppositeModel.fields.some check) with
explicit error handling (throw or processLogger.error + throw) so name
collisions are surfaced instead of being skipped.

---

Outside diff comments:
In `@packages/cli/src/actions/migrate.ts`:
- Around line 50-58: The current run() unconditionally calls
requireDataSourceUrl(schemaFile) and generateTempPrismaSchema(schemaFile,
prismaSchemaDir), which breaks URL-to-URL and migrations-only diff modes; move
schema validation/generation into runDiff or guard it with checks on the diff
modes so a temp schema is only created when a schema-based side is required
(e.g., when fromSchemaDatamodel or toSchemaDatamodel equals a schema mode).
Concretely, remove or defer the requireDataSourceUrl/schema generation from
run(), and in runDiff (or the call site that inspects diff mode) call
requireDataSourceUrl and generateTempPrismaSchema only when fromSchemaDatamodel
=== 'schema' or toSchemaDatamodel === 'schema' (or whichever enum/value you use
for schema-based modes), preserving the existing prismaSchemaDir logic when
constructing the temp schema.

---

Nitpick comments:
In `@packages/cli/src/actions/generate.ts`:
- Around line 189-193: Extract the repeated plugin source-path resolution into a
shared helper in action-utils.ts (e.g., resolvePluginSourceDir or
getPluginSourceDir) that accepts a plugin node and a fallback schemaFile and
returns the directory path (using
plugin.$cstNode?.parent?.element.$document?.uri?.fsPath ?? schemaFile then
path.dirname(...)); replace the inlined logic in generate.ts (where cliPlugin is
loaded with loadPluginModule(provider, path.dirname(pluginSourcePath))) and the
similar code in check.ts to call the new helper and pass its result into
loadPluginModule so both places share the same resolution behavior.

In `@packages/cli/test/migrate.test.ts`:
- Around line 73-76: The test only checks that migrate diff doesn't throw;
enhance it by capturing runCli's stdout/stderr and asserting the --script output
contains expected SQL diff text (e.g., "CREATE TABLE", table/column names from
the provided model, or specific ALTER/CREATE statements) to validate the
generated SQL; update the test case in migrate.test.ts (the "supports migrate
diff with --from-empty and --to-schema-datamodel" it block) to call runCli in a
way that returns stdout/stderr and add assertions against that output rather
than relying solely on no exception.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 218dfde8-e071-42b3-b015-88e6eca927af

📥 Commits

Reviewing files that changed from the base of the PR and between 0778e49 and 83a882d.

📒 Files selected for processing (12)
  • packages/cli/src/actions/action-utils.ts
  • packages/cli/src/actions/check.ts
  • packages/cli/src/actions/generate.ts
  • packages/cli/src/actions/migrate.ts
  • packages/cli/src/index.ts
  • packages/cli/test/generate.test.ts
  • packages/cli/test/migrate.test.ts
  • packages/cli/test/plugins/prisma-plugin.test.ts
  • packages/language/res/stdlib.zmodel
  • packages/language/src/validators/datamodel-validator.ts
  • packages/sdk/src/prisma/prisma-builder.ts
  • packages/sdk/src/prisma/prisma-schema-generator.ts

@elliots elliots force-pushed the multiple-files-fixes branch from 83a882d to 7c5318b Compare March 15, 2026 09:24
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/cli/test/generate.test.ts (1)

287-305: Optional: extract plugin fixture writer to reduce duplication.

Both new tests hand-roll similar plugin module scaffolding; a small helper in packages/cli/test/utils.ts would keep future plugin-resolution tests shorter and easier to maintain.

Also applies to: 337-346

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/test/generate.test.ts` around lines 287 - 305, Extract the
repeated scaffolding that creates a fake node_modules plugin into a reusable
helper (e.g., createPluginFixture or writePluginModule) placed in
packages/cli/test/utils.ts; the helper should accept parameters for workDir,
packageName, main entry (index.ts), and file contents, and it should encapsulate
the mkdirSync, package.json write, and index.ts write logic currently duplicated
in generate.test.ts (the block creating node_modules/my-test-plugin and its
package.json/index.ts) and the similar block at 337-346; then replace the
duplicated code in generate.test.ts with calls to the new helper (referencing
the helper by name) to keep tests DRY and easier to maintain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/test/plugins/prisma-plugin.test.ts`:
- Around line 131-151: The test reveals that generateMissingOppositeRelations()
errors are being swallowed by the try-catch in generate (the block around the
generate.ts try { ... } catch { ... }), causing silent success; update the catch
in the generate() flow so it does not silently swallow exceptions from
generateMissingOppositeRelations(): either rethrow the caught error (or call
process.exit(1) after logging) so the process returns non-zero, or explicitly
detect and propagate validation/generation errors to the caller; ensure the
change references generateMissingOppositeRelations() and the try-catch in
generate so the field name collision causes a visible failure instead of a
silent missing schema file.

---

Nitpick comments:
In `@packages/cli/test/generate.test.ts`:
- Around line 287-305: Extract the repeated scaffolding that creates a fake
node_modules plugin into a reusable helper (e.g., createPluginFixture or
writePluginModule) placed in packages/cli/test/utils.ts; the helper should
accept parameters for workDir, packageName, main entry (index.ts), and file
contents, and it should encapsulate the mkdirSync, package.json write, and
index.ts write logic currently duplicated in generate.test.ts (the block
creating node_modules/my-test-plugin and its package.json/index.ts) and the
similar block at 337-346; then replace the duplicated code in generate.test.ts
with calls to the new helper (referencing the helper by name) to keep tests DRY
and easier to maintain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 730ab75b-9dae-4cec-93bc-06d31faf0494

📥 Commits

Reviewing files that changed from the base of the PR and between 83a882d and 7c5318b.

📒 Files selected for processing (11)
  • packages/cli/src/actions/check.ts
  • packages/cli/src/actions/generate.ts
  • packages/cli/src/actions/migrate.ts
  • packages/cli/src/index.ts
  • packages/cli/test/generate.test.ts
  • packages/cli/test/migrate.test.ts
  • packages/cli/test/plugins/prisma-plugin.test.ts
  • packages/language/res/stdlib.zmodel
  • packages/language/src/validators/datamodel-validator.ts
  • packages/sdk/src/prisma/prisma-builder.ts
  • packages/sdk/src/prisma/prisma-schema-generator.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/language/src/validators/datamodel-validator.ts
  • packages/cli/src/index.ts
  • packages/language/res/stdlib.zmodel
  • packages/cli/src/actions/generate.ts
  • packages/cli/src/actions/check.ts
  • packages/cli/src/actions/migrate.ts
  • packages/sdk/src/prisma/prisma-builder.ts
  • packages/cli/test/migrate.test.ts

@ymc9
Copy link
Copy Markdown
Member

ymc9 commented Mar 17, 2026

Hi @elliots , thanks for making this PR!

I have a few general thoughts:

  1. About migrate diff

    Do you use it frequently? I can't remember exactly, but maybe I intentionally left it out when working on v3. This command feels too much coupled with Prisma Migrate - which may be replaced by a zenstack-home-made migration engine in the future. Also, to wrap around prisma migrate diff, when specifying a ZModel as output, I believe we also need to handle the prisma-generated schema and merge it into ZModel. Can be a bit tricky and fragile.

  2. About one-direction relation

    This has been brought up quite a few times back in v2, and I think it's a very useful feature to have. Besides automatically filling the opposite side of relation on prisma schema level, I'm not sure if anything needs to be done on zenstack tool chain yet - the ORM API, zod schemas, tanstack hooks, etc.

    Shall we spin it out into a separate PR? It's more like a new feature.

@ymc9
Copy link
Copy Markdown
Member

ymc9 commented Mar 23, 2026

Hi @elliots , thanks for making this PR!

I have a few general thoughts:

  1. About migrate diff
    Do you use it frequently? I can't remember exactly, but maybe I intentionally left it out when working on v3. This command feels too much coupled with Prisma Migrate - which may be replaced by a zenstack-home-made migration engine in the future. Also, to wrap around prisma migrate diff, when specifying a ZModel as output, I believe we also need to handle the prisma-generated schema and merge it into ZModel. Can be a bit tricky and fragile.
  2. About one-direction relation
    This has been brought up quite a few times back in v2, and I think it's a very useful feature to have. Besides automatically filling the opposite side of relation on prisma schema level, I'm not sure if anything needs to be done on zenstack tool chain yet - the ORM API, zod schemas, tanstack hooks, etc.
    Shall we spin it out into a separate PR? It's more like a new feature.

Hi @elliots , since I haven't heard back from you about these comments, I've pushed a change to address them and will merge this PR once CI passes. Love to get the other part of the changes included in v3.5.

@ymc9 ymc9 changed the title A few small fixes fix: a few small fixes Mar 23, 2026
@ymc9 ymc9 merged commit a017550 into zenstackhq:dev Mar 23, 2026
8 checks passed
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.

2 participants