Skip to content

Commit f8d2ea3

Browse files
authored
feat: add option to enable only certain rules (#8)
1 parent 1f286bc commit f8d2ea3

File tree

17 files changed

+152
-36
lines changed

17 files changed

+152
-36
lines changed

.github/workflows/ci.yml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,32 @@ jobs:
3838
- name: Checkout
3939
uses: actions/checkout@v2
4040

41-
- name: Tests folders that should work
41+
- name: Test all rules
4242
uses: ./
4343
with:
44-
path: './stubs'
44+
path: './stubs/all_rules'
4545

46-
- name: Tests folders are ignored
46+
- name: Test ignore option
4747
uses: ./
4848
with:
49-
path: './stubs'
49+
path: './stubs/all_rules'
5050
ignore: |
5151
20250101000000_should_work
5252
20240101000000_should_also_work
53+
54+
- name: Test only name rules
55+
uses: ./
56+
with:
57+
path: './stubs/name_rules'
58+
rules: |
59+
date
60+
format
61+
62+
- name: Test only file rules
63+
uses: ./
64+
with:
65+
path: './stubs/files_rules'
66+
rules: |
67+
missing
68+
link
69+
transaction

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2025 Zoey Kaiser
3+
Copyright (c) 2025 SIDESTREAM GmbH
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
**Lint Prisma migrations** is a github action that can lint the names of your [Prisma Migrations](https://www.prisma.io/docs/orm/prisma-migrate) based on a set of rules. The action was originally developed for [SIDESTREAM](https://github.com/sidestream-tech/).
44

5-
The action checks that:
6-
- The name matches the format of [`YYYYMMDDHHMMSS_YOUR_MIGRATION_NAME`](https://regex101.com/r/GoZmJG/1)
7-
- The date specified inside the migration name is not in the future
5+
## Rules
86

9-
## Example
7+
This action currently supports the following rules:
8+
- `format`: The name of the migration folder matches [`YYYYMMDDHHMMSS_YOUR_MIGRATION_NAME`](https://regex101.com/r/GoZmJG/1)
9+
- `date`: The date specified inside the migration name is not in the future
10+
- `missing`: The migration folder contains a `migration.sql` file
11+
- `link`: The migration begins with a link to the PR it was added it: `-- https://github.com/ORG/REPO/pull/NUMBER`
12+
- `transaction`: The migration is wrapped with a `BEGIN;` and `COMMIT;` [transaction block](https://www.postgresql.org/docs/current/tutorial-transactions.html)
13+
14+
## Configuration
1015

1116
```yml
1217
jobs:
@@ -20,13 +25,17 @@ jobs:
2025
uses: sidestream-tech/lint-prisma-migrations@1.1.0
2126
with:
2227
path: ./prisma/migrations/
28+
rules: |
29+
format
30+
date
31+
missing
32+
link
33+
transaction
2334
ignore: |
2435
20260101000000_ignore_me
2536
20270101000000_ignore_me_too
2637
```
2738
28-
## Inputs
29-
3039
### `path`
3140

3241
The path to the Prisma migrations folder.
@@ -35,6 +44,26 @@ The path to the Prisma migrations folder.
3544

3645
A multiline input of migration names to ignore. Helpful if these were already applied and their naming cannot be fixed.
3746

47+
### `rules`
48+
49+
A multiline input of the rules you would like to run again your migrations. If not set all rules with be enabled by default.
50+
51+
## Development
52+
53+
```sh
54+
# Install dependencies
55+
pnpm install
56+
57+
# Run unit tests
58+
pnpm test
59+
60+
# Build the project
61+
pnpm build
62+
63+
# Package the build (run after `pnpm build`)
64+
pnpm package
65+
```
66+
3867
## Credits
3968

4069
This action was inspired by https://github.com/batista/lint-filenames

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ inputs:
1111
ignore:
1212
description: 'A list of migration names to ignore'
1313
required: false
14+
rules:
15+
description: 'The list of rules you would like to run'
16+
required: false
1417
outputs:
1518
total-files-analyzed:
1619
description: 'The number of files analyzed.'

dist/index.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ function run() {
5757
return __awaiter(this, void 0, void 0, function* () {
5858
try {
5959
const path = core.getInput('path', { required: false }) || DEFAULT_PATH;
60-
const ignore = core.getMultilineInput('ignore', { required: false }) || [];
61-
const output = yield (0, validate_1.validate)(path, ignore);
60+
const ignore = core.getMultilineInput('ignore', { required: false });
61+
const rules = core.getMultilineInput('rules', { required: false });
62+
const output = yield (0, validate_1.validate)(path, { ignore, rules });
6263
core.setOutput('total-files-analyzed', output.totalFilesAnalyzed);
6364
// Get the JSON webhook payload for the event that triggered the workflow
6465
const payload = JSON.stringify(github.context.payload, undefined, 2);
@@ -176,10 +177,12 @@ const date_1 = __nccwpck_require__(8752);
176177
const format_1 = __nccwpck_require__(4719);
177178
const link_1 = __nccwpck_require__(4656);
178179
const transaction_1 = __nccwpck_require__(4354);
179-
function validate(path, ignore) {
180+
const DEFAULT_RULES = ['date', 'format', 'missing', 'link', 'transaction'];
181+
function validate(path, options) {
180182
return __awaiter(this, void 0, void 0, function* () {
181183
var _a, e_1, _b, _c;
182-
console.log(`Validating migrations at ${path}`);
184+
const rules = options.rules.length > 0 ? options.rules : DEFAULT_RULES;
185+
console.log(`Validating migrations at ${path} with rules: ${rules.join(', ')}`);
183186
console.log('---------------------------------------------------------');
184187
const opendir = node_fs_1.default.promises.opendir;
185188
const existsSync = node_fs_1.default.existsSync;
@@ -198,39 +201,39 @@ function validate(path, ignore) {
198201
continue;
199202
}
200203
// Check if migration is in ignore folder
201-
if (ignore.includes(dirent.name)) {
204+
if (options.ignore.includes(dirent.name)) {
202205
console.log(`🟠 Migration ${dirent.name} is ignored`);
203206
continue;
204207
}
205208
totalFilesAnalyzed++;
206209
// Test 1: Does the name match the pattern?
207-
if (!(0, format_1.isFormatValid)(dirent.name)) {
210+
if (!(0, format_1.isFormatValid)(dirent.name) && rules.includes('format')) {
208211
console.log(`❌ Migration ${dirent.name} is invalid format`);
209212
failedFiles.push({ name: dirent.name, reason: 'format' });
210213
continue;
211214
}
212215
// Test 2: Is the date in the folder name in the past?
213-
if (!(0, date_1.isDateValid)(dirent.name)) {
216+
if (!(0, date_1.isDateValid)(dirent.name) && rules.includes('date')) {
214217
console.log(`❌ Migration ${dirent.name} is invalid date`);
215218
failedFiles.push({ name: dirent.name, reason: 'date' });
216219
continue;
217220
}
218221
// Test 3: Does the migration folder contain a migration.sql file?
219222
const filePath = (0, ufo_1.joinURL)(dirent.parentPath, dirent.name, 'migration.sql');
220-
if (!existsSync(filePath)) {
223+
if (!existsSync(filePath) && rules.includes('missing')) {
221224
console.log(`❌ Migration ${dirent.name} does not contain a migration.sql file`);
222225
failedFiles.push({ name: dirent.name, reason: 'missing' });
223226
continue;
224227
}
225228
const migration = yield readFile(filePath, 'utf8');
226229
// Test 4: Does the migration file have a PR linked at the top?
227-
if (!(0, link_1.hasPRLink)(migration)) {
230+
if (!(0, link_1.hasPRLink)(migration) && rules.includes('link')) {
228231
console.log(`❌ Migration ${dirent.name} does not have a PR linked at the top of the migration`);
229232
failedFiles.push({ name: dirent.name, reason: 'link' });
230233
continue;
231234
}
232235
// Test 5: Is the migration wrapped in a transaction block?
233-
if (!(0, transaction_1.hasTransactionWrapper)(migration)) {
236+
if (!(0, transaction_1.hasTransactionWrapper)(migration) && rules.includes('transaction')) {
234237
console.log(`❌ Migration ${dirent.name} is not wrapped in a transaction block`);
235238
failedFiles.push({ name: dirent.name, reason: 'transaction' });
236239
continue;

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ const DEFAULT_PATH = './prisma/migrations/'
88
async function run(): Promise<void> {
99
try {
1010
const path = core.getInput('path', { required: false }) || DEFAULT_PATH
11-
const ignore = core.getMultilineInput('ignore', { required: false }) || []
11+
const ignore = core.getMultilineInput('ignore', { required: false })
12+
const rules = core.getMultilineInput('rules', { required: false })
1213

13-
const output = await validate(path, ignore)
14+
const output = await validate(path, { ignore, rules })
1415

1516
core.setOutput('total-files-analyzed', output.totalFilesAnalyzed)
1617

src/validate.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,25 @@ import { isFormatValid } from './rules/format'
55
import { hasPRLink } from './rules/link'
66
import { hasTransactionWrapper } from './rules/transaction'
77

8-
export async function validate(path: string, ignore: string[]) {
9-
console.log(`Validating migrations at ${path}`)
8+
type Rule = 'format' | 'date' | 'missing' | 'link' | 'transaction'
9+
const DEFAULT_RULES: Rule[] = ['date', 'format', 'missing', 'link', 'transaction']
10+
11+
interface ValidateOptions {
12+
ignore: string[]
13+
rules: string[]
14+
}
15+
16+
export async function validate(path: string, options: ValidateOptions) {
17+
const rules = options.rules.length > 0 ? options.rules : DEFAULT_RULES
18+
19+
console.log(`Validating migrations at ${path} with rules: ${rules.join(', ')}`)
1020
console.log('---------------------------------------------------------')
1121

1222
const opendir = fs.promises.opendir
1323
const existsSync = fs.existsSync
1424
const readFile = fs.promises.readFile
1525

16-
const failedFiles: {
17-
name: string
18-
reason: 'format' | 'date' | 'missing' | 'link' | 'transaction'
19-
}[] = []
26+
const failedFiles: { name: string, reason: Rule }[] = []
2027
let totalFilesAnalyzed = 0
2128

2229
try {
@@ -29,30 +36,30 @@ export async function validate(path: string, ignore: string[]) {
2936
}
3037

3138
// Check if migration is in ignore folder
32-
if (ignore.includes(dirent.name)) {
39+
if (options.ignore.includes(dirent.name)) {
3340
console.log(`🟠 Migration ${dirent.name} is ignored`)
3441
continue
3542
}
3643

3744
totalFilesAnalyzed++
3845

3946
// Test 1: Does the name match the pattern?
40-
if (!isFormatValid(dirent.name)) {
47+
if (!isFormatValid(dirent.name) && rules.includes('format')) {
4148
console.log(`❌ Migration ${dirent.name} is invalid format`)
4249
failedFiles.push({ name: dirent.name, reason: 'format' })
4350
continue
4451
}
4552

4653
// Test 2: Is the date in the folder name in the past?
47-
if (!isDateValid(dirent.name)) {
54+
if (!isDateValid(dirent.name) && rules.includes('date')) {
4855
console.log(`❌ Migration ${dirent.name} is invalid date`)
4956
failedFiles.push({ name: dirent.name, reason: 'date' })
5057
continue
5158
}
5259

5360
// Test 3: Does the migration folder contain a migration.sql file?
5461
const filePath = joinURL(dirent.parentPath, dirent.name, 'migration.sql')
55-
if (!existsSync(filePath)) {
62+
if (!existsSync(filePath) && rules.includes('missing')) {
5663
console.log(`❌ Migration ${dirent.name} does not contain a migration.sql file`)
5764
failedFiles.push({ name: dirent.name, reason: 'missing' })
5865
continue
@@ -61,14 +68,14 @@ export async function validate(path: string, ignore: string[]) {
6168
const migration = await readFile(filePath, 'utf8')
6269

6370
// Test 4: Does the migration file have a PR linked at the top?
64-
if (!hasPRLink(migration)) {
71+
if (!hasPRLink(migration) && rules.includes('link')) {
6572
console.log(`❌ Migration ${dirent.name} does not have a PR linked at the top of the migration`)
6673
failedFiles.push({ name: dirent.name, reason: 'link' })
6774
continue
6875
}
6976

7077
// Test 5: Is the migration wrapped in a transaction block?
71-
if (!hasTransactionWrapper(migration)) {
78+
if (!hasTransactionWrapper(migration) && rules.includes('transaction')) {
7279
console.log(`❌ Migration ${dirent.name} is not wrapped in a transaction block`)
7380
failedFiles.push({ name: dirent.name, reason: 'transaction' })
7481
continue

stubs/20240101000000_should_also_work/migration.sql renamed to stubs/all_rules/20240101000000_should_also_work/migration.sql

File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)