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: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"ios": "cd apps/expo && bun ios",
"lefthook": "lefthook install",
"lint": "biome check --write",
"lint:custom": "bun run scripts/lint/no-raw-typeof.ts && bun run scripts/lint/no-raw-regex.ts && bun run packages/env/scripts/no-raw-process-env.ts && bun run scripts/lint/no-duplicate-guards.ts && bun run scripts/lint/no-unauth-routes.ts",
"lint:custom": "bun run scripts/lint/no-raw-typeof.ts && bun run scripts/lint/no-raw-regex.ts && bun run packages/env/scripts/no-raw-process-env.ts && bun run scripts/lint/no-duplicate-guards.ts && bun run scripts/lint/no-unauth-routes.ts && bun run scripts/lint/check-drizzle-migrations.ts",
"lint:strict": "biome check && bun run lint:custom",
"lint-unsafe": "biome check --write --unsafe",
"mcp": "bun run --cwd packages/mcp dev",
Expand Down
4 changes: 4 additions & 0 deletions scripts/check-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ const ALL_CHECKS: CheckDef[] = [
name: 'no-unauth-routes',
script: join(ROOT, 'scripts', 'lint', 'no-unauth-routes.ts'),
},
{
name: 'check-drizzle-migrations',
script: join(ROOT, 'scripts', 'lint', 'check-drizzle-migrations.ts'),
},
{
name: 'check-type-casts',
script: join(ROOT, 'packages', 'checks', 'src', 'check-type-casts.ts'),
Expand Down
60 changes: 60 additions & 0 deletions scripts/lint/check-drizzle-migrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bun

import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

const ROOT = join(import.meta.dir, '..', '..');

const MIGRATION_TARGETS = [
{ name: 'packages/api', allowManual: new Set(['0010_great_colleen_wing.sql']) },
{ name: 'packages/osm-db', allowManual: new Set(['0000_extensions.sql']) },
];
Comment on lines +8 to +11
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Auto-discover drizzle targets to avoid silent policy gaps.

Line 8 hard-codes MIGRATION_TARGETS, so new packages/*/drizzle folders can bypass this check until someone updates this file.

Proposed refactor
 import { existsSync, readdirSync, readFileSync } from 'node:fs';
 import { join } from 'node:path';

 const ROOT = join(import.meta.dir, '..', '..');

-const MIGRATION_TARGETS = [
-  { name: 'packages/api', allowManual: new Set(['0010_great_colleen_wing.sql']) },
-  { name: 'packages/osm-db', allowManual: new Set(['0000_extensions.sql']) },
-];
+const ALLOW_MANUAL_BY_PACKAGE = new Map<string, Set<string>>([
+  ['packages/api', new Set(['0010_great_colleen_wing.sql'])],
+  ['packages/osm-db', new Set(['0000_extensions.sql'])],
+]);
+
+const MIGRATION_TARGETS = readdirSync(join(ROOT, 'packages'), { withFileTypes: true })
+  .filter((d) => d.isDirectory())
+  .map((d) => `packages/${d.name}`)
+  .filter((name) => existsSync(join(ROOT, name, 'drizzle')))
+  .map((name) => ({
+    name,
+    allowManual: ALLOW_MANUAL_BY_PACKAGE.get(name) ?? new Set<string>(),
+  }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/lint/check-drizzle-migrations.ts` around lines 8 - 11,
MIGRATION_TARGETS is hard-coded so new packages with a drizzle folder are
missed; replace the static MIGRATION_TARGETS constant with code that
auto-discovers packages/*/drizzle (e.g., using fs.readdir or glob) and builds
the array of targets (each with name and allowManual Set), preserving existing
allowManual entries for known packages (0010_great_colleen_wing.sql and
0000_extensions.sql) and defaulting to an empty Set for others so the lint check
covers all current and future drizzle folders.


const DRIZZLE_FILE_PATTERN = /^\d{4}_[a-z0-9]+(?:_[a-z0-9]+)+\.sql$/;
const DRIZZLE_TEMPLATE_COMMENT = '-- Custom SQL migration file, put your code below! --';

interface Violation {
packageName: string;
message: string;
}

function checkTarget(target: (typeof MIGRATION_TARGETS)[number], violations: Violation[]): void {
const drizzleDir = join(ROOT, target.name, 'drizzle');
if (!existsSync(drizzleDir)) return;

const sqlFiles = readdirSync(drizzleDir)
.filter((file) => file.endsWith('.sql'))
.sort();

for (const file of sqlFiles) {
if (target.allowManual.has(file)) continue;

if (!DRIZZLE_FILE_PATTERN.test(file)) {
violations.push({
packageName: target.name,
message: `${file}: migration name must match drizzle-kit format (NNNN_word_word.sql)`,
});
}

const content = readFileSync(join(drizzleDir, file), 'utf-8');
if (content.includes(DRIZZLE_TEMPLATE_COMMENT)) {
violations.push({
packageName: target.name,
message: `${file}: contains drizzle template comment; regenerate via drizzle-kit instead of hand-writing`,
});
}
}
}

const violations: Violation[] = [];
for (const target of MIGRATION_TARGETS) checkTarget(target, violations);

if (violations.length > 0) {
console.log(`Drizzle migration checks failed (${violations.length}):\n`);
for (const violation of violations) {
console.log(`${violation.packageName}: ${violation.message}`);
}
Comment on lines +53 to +56
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Print per-package regeneration commands in failures.

Current output reports the violation but not the exact recovery command, which slows remediation.

Proposed improvement
 if (violations.length > 0) {
   console.log(`Drizzle migration checks failed (${violations.length}):\n`);
   for (const violation of violations) {
     console.log(`${violation.packageName}: ${violation.message}`);
   }
+  console.log('\nSuggested fix commands:');
+  for (const pkg of new Set(violations.map((v) => v.packageName))) {
+    console.log(`  bun --cwd ${pkg} drizzle-kit generate`);
+  }
   process.exit(1);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(`Drizzle migration checks failed (${violations.length}):\n`);
for (const violation of violations) {
console.log(`${violation.packageName}: ${violation.message}`);
}
console.log(`Drizzle migration checks failed (${violations.length}):\n`);
for (const violation of violations) {
console.log(`${violation.packageName}: ${violation.message}`);
}
console.log('\nSuggested fix commands:');
for (const pkg of new Set(violations.map((v) => v.packageName))) {
console.log(` bun --cwd ${pkg} drizzle-kit generate`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/lint/check-drizzle-migrations.ts` around lines 53 - 56, Update the
failure logging to include a per-package regeneration command alongside the
violation details: when iterating the violations array (violations,
violation.packageName, violation.message), print a suggested shell command that
uses violation.packageName to regenerate migrations (for example: cd
packages/${violation.packageName} && npm run generate-migrations or npm run
db:migrate:generate --workspace=${violation.packageName}) so maintainers can
copy-paste the recovery step directly; ensure the command string is clearly
labeled and uses the packageName variable.

process.exit(1);
}

console.log('Drizzle migration checks passed.');
Loading