Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- [#790](https://github.com/loop-payments/prisma-lint/issues/790) Add support for Prisma 7 config file schema path configuration (`prisma.config.ts` or `.config/prisma.ts`). The `package.json#prisma.schema` field remains supported for backwards compatibility.

## 0.12.0 (2025-12-04)

- [#355](https://github.com/loop-payments/prisma-lint/issues/355) Add new `/// prisma-lint-ignore-field` comment to ignore specific fields.
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ A linter for Prisma schema files.
> yarn prisma-lint
```

The default schema path is `prisma/schema.prisma`. If a custom schema path is specified in the field `prisma.schema` within `package.json`, that is used instead.
The default schema path is `prisma/schema.prisma`. The schema path can also be configured in:

1. **`prisma.config.ts`** or **`.config/prisma.ts`** (Prisma 7+): The `schema` field is used if present.
2. **`package.json#prisma.schema`** (legacy): Supported for backwards compatibility.

Alternatively, you can provide one or more explicit paths as CLI arguments. These can be globs, directories, or file paths.

Expand Down
34 changes: 29 additions & 5 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { readPackageUp } from 'read-package-up';

import { getTruncatedFileName } from '#src/common/file.js';
import { parseRules } from '#src/common/parse-rules.js';
import { getSchemaPathFromPrismaConfig } from '#src/common/prisma-config.js';
import { lintPrismaFiles } from '#src/lint-prisma-files.js';
import { outputToConsole } from '#src/output/console.js';
import ruleDefinitions from '#src/rule-definitions.js';
Expand Down Expand Up @@ -51,6 +52,29 @@ const getSchemaFromPackageJson = async (cwd: string) => {
return pkgJson?.packageJson?.prisma?.schema;
};

/**
* Gets the schema path from available configuration sources.
* Priority order:
* 1. prisma.config.ts (Prisma 7+)
* 2. package.json#prisma.schema (legacy, deprecated in Prisma 7)
* 3. null (will fall back to default)
*/
const getSchemaFromConfig = async (cwd: string) => {
// First try prisma.config.ts (Prisma 7+)
const schemaFromPrismaConfig = await getSchemaPathFromPrismaConfig(cwd);
if (schemaFromPrismaConfig != null) {
return schemaFromPrismaConfig;
}

// Fall back to package.json#prisma.schema (legacy)
const schemaFromPackageJson = await getSchemaFromPackageJson(cwd);
if (schemaFromPackageJson != null) {
return schemaFromPackageJson;
}

return null;
};

const getRootConfigResult = async () => {
if (options.config != null) {
const result = await explorer.load(options.config);
Expand All @@ -67,13 +91,13 @@ const getRootConfigResult = async () => {
return result;
};

const getPathsFromArgsOrPackageJson = async (args: string[]) => {
const getPathsFromArgsOrConfig = async (args: string[]) => {
if (args.length > 0) {
return args;
}
const schemaFromPackageJson = await getSchemaFromPackageJson(process.cwd());
if (schemaFromPackageJson != null) {
return [schemaFromPackageJson];
const schemaFromConfig = await getSchemaFromConfig(process.cwd());
if (schemaFromConfig != null) {
return [schemaFromConfig];
}
return [DEFAULT_PRISMA_FILE_PATH];
};
Expand Down Expand Up @@ -141,7 +165,7 @@ const run = async () => {
}
}

const paths = await getPathsFromArgsOrPackageJson(args);
const paths = await getPathsFromArgsOrConfig(args);
const fileNames = resolvePrismaFileNames(paths);
const fileViolationList = await lintPrismaFiles({
rules,
Expand Down
217 changes: 217 additions & 0 deletions src/common/prisma-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import fs from 'fs';
import os from 'os';
import path from 'path';

import {
findPrismaConfigFile,
getSchemaPathFromPrismaConfig,
} from '#src/common/prisma-config.js';

describe('findPrismaConfigFile', () => {
let tempDir: string;

beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'prisma-lint-test-'));
});

afterEach(() => {
fs.rmSync(tempDir, { recursive: true });
});

it('returns null when no config file exists', () => {
const result = findPrismaConfigFile(tempDir);
expect(result).toBeNull();
});

it('finds prisma.config.ts in current directory', () => {
const configPath = path.join(tempDir, 'prisma.config.ts');
fs.writeFileSync(configPath, 'export default {}');

const result = findPrismaConfigFile(tempDir);
expect(result).toBe(configPath);
});

it('finds prisma.config.js in current directory', () => {
const configPath = path.join(tempDir, 'prisma.config.js');
fs.writeFileSync(configPath, 'module.exports = {}');

const result = findPrismaConfigFile(tempDir);
expect(result).toBe(configPath);
});

it('finds prisma.config.mjs in current directory', () => {
const configPath = path.join(tempDir, 'prisma.config.mjs');
fs.writeFileSync(configPath, 'export default {}');

const result = findPrismaConfigFile(tempDir);
expect(result).toBe(configPath);
});

it('prefers .ts over .js', () => {
const tsConfigPath = path.join(tempDir, 'prisma.config.ts');
const jsConfigPath = path.join(tempDir, 'prisma.config.js');
fs.writeFileSync(tsConfigPath, 'export default {}');
fs.writeFileSync(jsConfigPath, 'module.exports = {}');

const result = findPrismaConfigFile(tempDir);
expect(result).toBe(tsConfigPath);
});

it('finds config in parent directory', () => {
const childDir = path.join(tempDir, 'child');
fs.mkdirSync(childDir);

const configPath = path.join(tempDir, 'prisma.config.ts');
fs.writeFileSync(configPath, 'export default {}');

const result = findPrismaConfigFile(childDir);
expect(result).toBe(configPath);
});

it('finds config in grandparent directory', () => {
const childDir = path.join(tempDir, 'child');
const grandchildDir = path.join(childDir, 'grandchild');
fs.mkdirSync(childDir);
fs.mkdirSync(grandchildDir);

const configPath = path.join(tempDir, 'prisma.config.ts');
fs.writeFileSync(configPath, 'export default {}');

const result = findPrismaConfigFile(grandchildDir);
expect(result).toBe(configPath);
});

it('finds .config/prisma.ts in current directory', () => {
const configDir = path.join(tempDir, '.config');
fs.mkdirSync(configDir);
const configPath = path.join(configDir, 'prisma.ts');
fs.writeFileSync(configPath, 'export default {}');

const result = findPrismaConfigFile(tempDir);
expect(result).toBe(configPath);
});

it('finds .config/prisma.mjs in current directory', () => {
const configDir = path.join(tempDir, '.config');
fs.mkdirSync(configDir);
const configPath = path.join(configDir, 'prisma.mjs');
fs.writeFileSync(configPath, 'export default {}');

const result = findPrismaConfigFile(tempDir);
expect(result).toBe(configPath);
});

it('prefers root-level config over .config directory', () => {
const rootConfigPath = path.join(tempDir, 'prisma.config.ts');
fs.writeFileSync(rootConfigPath, 'export default {}');

const configDir = path.join(tempDir, '.config');
fs.mkdirSync(configDir);
const dotConfigPath = path.join(configDir, 'prisma.ts');
fs.writeFileSync(dotConfigPath, 'export default {}');

const result = findPrismaConfigFile(tempDir);
expect(result).toBe(rootConfigPath);
});

it('finds .config/prisma.ts in parent directory', () => {
const childDir = path.join(tempDir, 'child');
fs.mkdirSync(childDir);

const configDir = path.join(tempDir, '.config');
fs.mkdirSync(configDir);
const configPath = path.join(configDir, 'prisma.ts');
fs.writeFileSync(configPath, 'export default {}');

const result = findPrismaConfigFile(childDir);
expect(result).toBe(configPath);
});
});

describe('getSchemaPathFromPrismaConfig', () => {
let tempDir: string;

beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'prisma-lint-test-'));
});

afterEach(() => {
fs.rmSync(tempDir, { recursive: true });
});

it('returns null when no config file exists', async () => {
const result = await getSchemaPathFromPrismaConfig(tempDir);
expect(result).toBeNull();
});

it('returns null when config file has no schema', async () => {
const configPath = path.join(tempDir, 'prisma.config.mjs');
fs.writeFileSync(configPath, 'export default {}');

const result = await getSchemaPathFromPrismaConfig(tempDir);
expect(result).toBeNull();
});

it('returns schema path from plain object config', async () => {
const configPath = path.join(tempDir, 'prisma.config.mjs');
fs.writeFileSync(
configPath,
"export default { schema: 'prisma/schema.prisma' }",
);

const result = await getSchemaPathFromPrismaConfig(tempDir);
expect(result).toBe(path.join(tempDir, 'prisma/schema.prisma'));
});

it('returns schema path from function config (defineConfig pattern)', async () => {
const configPath = path.join(tempDir, 'prisma.config.mjs');
fs.writeFileSync(
configPath,
"export default () => ({ schema: 'src/prisma/schema.prisma' })",
);

const result = await getSchemaPathFromPrismaConfig(tempDir);
expect(result).toBe(path.join(tempDir, 'src/prisma/schema.prisma'));
});

it('resolves relative schema path from config directory', async () => {
const childDir = path.join(tempDir, 'child');
fs.mkdirSync(childDir);

const configPath = path.join(tempDir, 'prisma.config.mjs');
fs.writeFileSync(
configPath,
"export default { schema: 'prisma/schema.prisma' }",
);

const result = await getSchemaPathFromPrismaConfig(childDir);
// Schema path should be relative to config file location, not cwd
expect(result).toBe(path.join(tempDir, 'prisma/schema.prisma'));
});

it('preserves absolute schema path', async () => {
const absoluteSchemaPath = '/absolute/path/to/schema.prisma';
const configPath = path.join(tempDir, 'prisma.config.mjs');
fs.writeFileSync(
configPath,
`export default { schema: '${absoluteSchemaPath}' }`,
);

const result = await getSchemaPathFromPrismaConfig(tempDir);
expect(result).toBe(absoluteSchemaPath);
});

it('returns schema path from .config/prisma.mjs', async () => {
const configDir = path.join(tempDir, '.config');
fs.mkdirSync(configDir);
const configPath = path.join(configDir, 'prisma.mjs');
fs.writeFileSync(
configPath,
"export default { schema: '../prisma/schema.prisma' }",
);

const result = await getSchemaPathFromPrismaConfig(tempDir);
// Schema path is relative to config file location (.config directory)
expect(result).toBe(path.join(tempDir, 'prisma/schema.prisma'));
});
});
Loading