@nextsparkjs/ai-workflow 0.1.0-beta.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Validate Migration Script
|
|
4
|
+
|
|
5
|
+
Validates SQL migration files against project conventions.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python validate-migration.py --file PATH [--strict]
|
|
9
|
+
python validate-migration.py --path DIR [--strict]
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--file PATH Single migration file to validate
|
|
13
|
+
--path DIR Directory of migrations to validate
|
|
14
|
+
--strict Exit with error if issues found
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import re
|
|
20
|
+
import argparse
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import List, Dict, Tuple
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ValidationError:
|
|
26
|
+
"""Represents a validation issue."""
|
|
27
|
+
def __init__(self, severity: str, message: str, line: int = None, context: str = None):
|
|
28
|
+
self.severity = severity # 'error', 'warning', 'info'
|
|
29
|
+
self.message = message
|
|
30
|
+
self.line = line
|
|
31
|
+
self.context = context
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def validate_timestamptz(content: str, errors: List[ValidationError]) -> None:
|
|
35
|
+
"""Check that all timestamps use TIMESTAMPTZ, not TIMESTAMP."""
|
|
36
|
+
lines = content.split('\n')
|
|
37
|
+
for i, line in enumerate(lines, 1):
|
|
38
|
+
# Skip comments
|
|
39
|
+
if line.strip().startswith('--'):
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
# Check for plain TIMESTAMP (not TIMESTAMPTZ)
|
|
43
|
+
if re.search(r'\bTIMESTAMP\b(?!TZ)', line, re.IGNORECASE):
|
|
44
|
+
errors.append(ValidationError(
|
|
45
|
+
'error',
|
|
46
|
+
'Use TIMESTAMPTZ instead of TIMESTAMP',
|
|
47
|
+
i,
|
|
48
|
+
line.strip()[:60]
|
|
49
|
+
))
|
|
50
|
+
|
|
51
|
+
# Check for CURRENT_TIMESTAMP
|
|
52
|
+
if 'CURRENT_TIMESTAMP' in line.upper():
|
|
53
|
+
errors.append(ValidationError(
|
|
54
|
+
'warning',
|
|
55
|
+
'Use now() instead of CURRENT_TIMESTAMP',
|
|
56
|
+
i,
|
|
57
|
+
line.strip()[:60]
|
|
58
|
+
))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def validate_id_type(content: str, errors: List[ValidationError]) -> None:
|
|
62
|
+
"""Check that IDs use TEXT, not UUID type."""
|
|
63
|
+
lines = content.split('\n')
|
|
64
|
+
for i, line in enumerate(lines, 1):
|
|
65
|
+
if line.strip().startswith('--'):
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
# Check for UUID PRIMARY KEY (should be TEXT)
|
|
69
|
+
if re.search(r'\bid\s+UUID\s+PRIMARY\s+KEY', line, re.IGNORECASE):
|
|
70
|
+
errors.append(ValidationError(
|
|
71
|
+
'error',
|
|
72
|
+
'Use TEXT PRIMARY KEY, not UUID (Better Auth uses TEXT)',
|
|
73
|
+
i,
|
|
74
|
+
line.strip()[:60]
|
|
75
|
+
))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def validate_field_ordering(content: str, errors: List[ValidationError]) -> None:
|
|
79
|
+
"""Check that fields follow the required ordering."""
|
|
80
|
+
# Find CREATE TABLE blocks
|
|
81
|
+
create_pattern = r'CREATE\s+TABLE[^;]+;'
|
|
82
|
+
for match in re.finditer(create_pattern, content, re.IGNORECASE | re.DOTALL):
|
|
83
|
+
table_def = match.group()
|
|
84
|
+
|
|
85
|
+
# Extract field lines
|
|
86
|
+
lines = table_def.split('\n')
|
|
87
|
+
fields = []
|
|
88
|
+
for line in lines:
|
|
89
|
+
# Skip comments and empty lines
|
|
90
|
+
stripped = line.strip()
|
|
91
|
+
if not stripped or stripped.startswith('--') or stripped.startswith('CREATE') or stripped.startswith(')'):
|
|
92
|
+
continue
|
|
93
|
+
if stripped.startswith('CONSTRAINT'):
|
|
94
|
+
continue
|
|
95
|
+
fields.append(stripped)
|
|
96
|
+
|
|
97
|
+
if not fields:
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
# Check first field is 'id'
|
|
101
|
+
if fields and not fields[0].strip().lower().startswith('id '):
|
|
102
|
+
errors.append(ValidationError(
|
|
103
|
+
'warning',
|
|
104
|
+
'First field should be "id" (primary key)',
|
|
105
|
+
context=fields[0][:50]
|
|
106
|
+
))
|
|
107
|
+
|
|
108
|
+
# Check system fields are last
|
|
109
|
+
system_fields = ['createdAt', 'updatedAt', 'status']
|
|
110
|
+
found_system = False
|
|
111
|
+
for field in fields:
|
|
112
|
+
for sys_field in system_fields:
|
|
113
|
+
if f'"{sys_field}"' in field or field.strip().startswith(sys_field):
|
|
114
|
+
found_system = True
|
|
115
|
+
break
|
|
116
|
+
if found_system:
|
|
117
|
+
# After finding a system field, no business fields should appear
|
|
118
|
+
if not any(sf in field for sf in system_fields) and 'CONSTRAINT' not in field:
|
|
119
|
+
if re.match(r'^"\w+"\s+', field) and field.strip() != ')':
|
|
120
|
+
errors.append(ValidationError(
|
|
121
|
+
'info',
|
|
122
|
+
'System fields (createdAt, updatedAt) should be last',
|
|
123
|
+
context=field[:50]
|
|
124
|
+
))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def validate_meta_fk_naming(content: str, errors: List[ValidationError]) -> None:
|
|
128
|
+
"""Check that meta tables use "entityId" not entity-specific names."""
|
|
129
|
+
if '_metas' not in content.lower():
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
# Check for incorrect FK naming patterns
|
|
133
|
+
wrong_patterns = [
|
|
134
|
+
(r'"postId"\s+TEXT\s+NOT\s+NULL\s+REFERENCES', 'Use "entityId" instead of "postId"'),
|
|
135
|
+
(r'"userId"\s+TEXT\s+NOT\s+NULL\s+REFERENCES.*metas', 'Use "entityId" instead of "userId" for meta table'),
|
|
136
|
+
(r'"clientId"\s+TEXT\s+NOT\s+NULL\s+REFERENCES', 'Use "entityId" instead of "clientId"'),
|
|
137
|
+
(r'"taskId"\s+TEXT\s+NOT\s+NULL\s+REFERENCES', 'Use "entityId" instead of "taskId"'),
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
for pattern, message in wrong_patterns:
|
|
141
|
+
if re.search(pattern, content, re.IGNORECASE):
|
|
142
|
+
errors.append(ValidationError(
|
|
143
|
+
'error',
|
|
144
|
+
message
|
|
145
|
+
))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def validate_child_fk_naming(content: str, errors: List[ValidationError]) -> None:
|
|
149
|
+
"""Check that child tables use "parentId" not parent-specific names."""
|
|
150
|
+
# Skip if it's a meta table
|
|
151
|
+
if '_metas' in content.lower():
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
# Check for child table patterns (has FK but no userId directly)
|
|
155
|
+
lines = content.split('\n')
|
|
156
|
+
for i, line in enumerate(lines, 1):
|
|
157
|
+
if line.strip().startswith('--'):
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
# Look for FK references in child tables that aren't "parentId"
|
|
161
|
+
if re.search(r'"(clientId|orderId|productId)"\s+TEXT\s+NOT\s+NULL\s+REFERENCES', line, re.IGNORECASE):
|
|
162
|
+
# Skip if it's the users table reference
|
|
163
|
+
if 'public."users"' in line:
|
|
164
|
+
continue
|
|
165
|
+
errors.append(ValidationError(
|
|
166
|
+
'info',
|
|
167
|
+
'Consider using "parentId" for child entity FK',
|
|
168
|
+
i,
|
|
169
|
+
line.strip()[:60]
|
|
170
|
+
))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def validate_rls_enabled(content: str, errors: List[ValidationError]) -> None:
|
|
174
|
+
"""Check that RLS is enabled for tables."""
|
|
175
|
+
# Find table names
|
|
176
|
+
table_pattern = r'CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?public\."(\w+)"'
|
|
177
|
+
tables = re.findall(table_pattern, content, re.IGNORECASE)
|
|
178
|
+
|
|
179
|
+
for table in tables:
|
|
180
|
+
# Check if RLS is enabled
|
|
181
|
+
rls_pattern = rf'ALTER\s+TABLE\s+public\."{table}"\s+ENABLE\s+ROW\s+LEVEL\s+SECURITY'
|
|
182
|
+
if not re.search(rls_pattern, content, re.IGNORECASE):
|
|
183
|
+
errors.append(ValidationError(
|
|
184
|
+
'warning',
|
|
185
|
+
f'RLS not enabled for table "{table}"',
|
|
186
|
+
))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def validate_cascade_usage(content: str, errors: List[ValidationError]) -> None:
|
|
190
|
+
"""Check that main tables use DROP CASCADE."""
|
|
191
|
+
# Find DROP TABLE statements
|
|
192
|
+
drop_pattern = r'DROP\s+TABLE\s+IF\s+EXISTS\s+public\."(\w+)"'
|
|
193
|
+
for match in re.finditer(drop_pattern, content, re.IGNORECASE):
|
|
194
|
+
table = match.group(1)
|
|
195
|
+
full_match = match.group(0)
|
|
196
|
+
|
|
197
|
+
# Skip meta tables
|
|
198
|
+
if '_metas' in table.lower():
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
# Check if CASCADE is used
|
|
202
|
+
line_end = content[match.end():match.end()+20]
|
|
203
|
+
if 'CASCADE' not in line_end.upper():
|
|
204
|
+
errors.append(ValidationError(
|
|
205
|
+
'warning',
|
|
206
|
+
f'Use "DROP TABLE IF EXISTS ... CASCADE" for main table "{table}"',
|
|
207
|
+
))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def validate_trigger_function(content: str, errors: List[ValidationError]) -> None:
|
|
211
|
+
"""Check that triggers use existing Better Auth function."""
|
|
212
|
+
if 'set_updated_at' in content.lower():
|
|
213
|
+
# Check for function redefinition
|
|
214
|
+
if 'CREATE OR REPLACE FUNCTION' in content and 'set_updated_at' in content:
|
|
215
|
+
errors.append(ValidationError(
|
|
216
|
+
'error',
|
|
217
|
+
'Do not redefine set_updated_at() - use existing Better Auth function',
|
|
218
|
+
))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def validate_file(file_path: Path) -> List[ValidationError]:
|
|
222
|
+
"""Validate a single migration file."""
|
|
223
|
+
errors = []
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
227
|
+
content = f.read()
|
|
228
|
+
except Exception as e:
|
|
229
|
+
errors.append(ValidationError('error', f'Could not read file: {e}'))
|
|
230
|
+
return errors
|
|
231
|
+
|
|
232
|
+
# Run all validations
|
|
233
|
+
validate_timestamptz(content, errors)
|
|
234
|
+
validate_id_type(content, errors)
|
|
235
|
+
validate_field_ordering(content, errors)
|
|
236
|
+
validate_meta_fk_naming(content, errors)
|
|
237
|
+
validate_child_fk_naming(content, errors)
|
|
238
|
+
validate_rls_enabled(content, errors)
|
|
239
|
+
validate_cascade_usage(content, errors)
|
|
240
|
+
validate_trigger_function(content, errors)
|
|
241
|
+
|
|
242
|
+
return errors
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def main():
|
|
246
|
+
parser = argparse.ArgumentParser(description='Validate migration files')
|
|
247
|
+
parser.add_argument('--file', help='Single migration file to validate')
|
|
248
|
+
parser.add_argument('--path', help='Directory of migrations to validate')
|
|
249
|
+
parser.add_argument('--strict', action='store_true', help='Exit with error if issues found')
|
|
250
|
+
|
|
251
|
+
args = parser.parse_args()
|
|
252
|
+
|
|
253
|
+
if not args.file and not args.path:
|
|
254
|
+
args.path = 'core/migrations/'
|
|
255
|
+
|
|
256
|
+
print(f"\n{'=' * 60}")
|
|
257
|
+
print("MIGRATION VALIDATION")
|
|
258
|
+
print(f"{'=' * 60}")
|
|
259
|
+
|
|
260
|
+
files_to_check = []
|
|
261
|
+
|
|
262
|
+
if args.file:
|
|
263
|
+
files_to_check.append(Path(args.file))
|
|
264
|
+
elif args.path:
|
|
265
|
+
path = Path(args.path)
|
|
266
|
+
if path.is_dir():
|
|
267
|
+
files_to_check = list(path.glob('*.sql'))
|
|
268
|
+
else:
|
|
269
|
+
print(f"Error: Path not found: {args.path}")
|
|
270
|
+
return 1
|
|
271
|
+
|
|
272
|
+
print(f"Checking {len(files_to_check)} file(s)")
|
|
273
|
+
print(f"{'=' * 60}\n")
|
|
274
|
+
|
|
275
|
+
total_errors = 0
|
|
276
|
+
total_warnings = 0
|
|
277
|
+
files_with_issues = 0
|
|
278
|
+
|
|
279
|
+
for file_path in sorted(files_to_check):
|
|
280
|
+
errors = validate_file(file_path)
|
|
281
|
+
|
|
282
|
+
if errors:
|
|
283
|
+
files_with_issues += 1
|
|
284
|
+
print(f"\n{file_path.name}:")
|
|
285
|
+
print("-" * 40)
|
|
286
|
+
|
|
287
|
+
for error in errors:
|
|
288
|
+
if error.severity == 'error':
|
|
289
|
+
total_errors += 1
|
|
290
|
+
prefix = " ERROR"
|
|
291
|
+
elif error.severity == 'warning':
|
|
292
|
+
total_warnings += 1
|
|
293
|
+
prefix = " WARN "
|
|
294
|
+
else:
|
|
295
|
+
prefix = " INFO "
|
|
296
|
+
|
|
297
|
+
line_info = f" (line {error.line})" if error.line else ""
|
|
298
|
+
print(f"{prefix}: {error.message}{line_info}")
|
|
299
|
+
if error.context:
|
|
300
|
+
print(f" → {error.context}")
|
|
301
|
+
|
|
302
|
+
# Summary
|
|
303
|
+
print(f"\n{'=' * 60}")
|
|
304
|
+
print("SUMMARY")
|
|
305
|
+
print(f"{'=' * 60}")
|
|
306
|
+
print(f" Files checked: {len(files_to_check)}")
|
|
307
|
+
print(f" Files with issues: {files_with_issues}")
|
|
308
|
+
print(f" Errors: {total_errors}")
|
|
309
|
+
print(f" Warnings: {total_warnings}")
|
|
310
|
+
print(f"{'=' * 60}\n")
|
|
311
|
+
|
|
312
|
+
if total_errors == 0 and total_warnings == 0:
|
|
313
|
+
print("All migrations follow conventions.")
|
|
314
|
+
return 0
|
|
315
|
+
elif args.strict and total_errors > 0:
|
|
316
|
+
print("Strict mode: Exiting with error due to validation issues.")
|
|
317
|
+
return 1
|
|
318
|
+
else:
|
|
319
|
+
return 0
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
if __name__ == '__main__':
|
|
323
|
+
sys.exit(main())
|