@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,382 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generate Migration Script
|
|
4
|
+
|
|
5
|
+
Generates SQL migration from entity configuration.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python generate-migration.py --entity ENTITY_NAME [--theme THEME] [--type TYPE]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--entity ENTITY_NAME Name of the entity (kebab-case)
|
|
12
|
+
--theme THEME Theme name (default: from NEXT_PUBLIC_ACTIVE_THEME or 'default')
|
|
13
|
+
--type TYPE Migration type: table, metas, index, rls (default: table)
|
|
14
|
+
--output OUTPUT Output file (default: stdout)
|
|
15
|
+
--with-rls Include RLS policies
|
|
16
|
+
--with-indexes Include optimized indexes
|
|
17
|
+
--soft-delete Add soft delete columns
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import argparse
|
|
23
|
+
import json
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
from typing import Dict, List, Optional
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_active_theme() -> str:
|
|
30
|
+
"""Get active theme from environment or default."""
|
|
31
|
+
return os.environ.get('NEXT_PUBLIC_ACTIVE_THEME', 'default')
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def to_snake_case(name: str) -> str:
|
|
35
|
+
"""Convert kebab-case to snake_case."""
|
|
36
|
+
return name.replace('-', '_')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def to_camel_case(name: str) -> str:
|
|
40
|
+
"""Convert kebab-case to camelCase."""
|
|
41
|
+
components = name.split('-')
|
|
42
|
+
return components[0] + ''.join(x.title() for x in components[1:])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Field type to SQL type mapping
|
|
46
|
+
FIELD_TYPE_MAP = {
|
|
47
|
+
'text': 'VARCHAR(255)',
|
|
48
|
+
'textarea': 'TEXT',
|
|
49
|
+
'number': 'DECIMAL',
|
|
50
|
+
'boolean': 'BOOLEAN',
|
|
51
|
+
'date': 'DATE',
|
|
52
|
+
'datetime': 'TIMESTAMPTZ',
|
|
53
|
+
'email': 'VARCHAR(255)',
|
|
54
|
+
'url': 'TEXT',
|
|
55
|
+
'json': 'JSONB',
|
|
56
|
+
'select': 'VARCHAR(100)',
|
|
57
|
+
'multiselect': 'TEXT[]',
|
|
58
|
+
'tags': 'TEXT[]',
|
|
59
|
+
'relation': 'UUID',
|
|
60
|
+
'relation-multi': 'UUID[]',
|
|
61
|
+
'reference': 'UUID',
|
|
62
|
+
'user': 'UUID',
|
|
63
|
+
'phone': 'VARCHAR(50)',
|
|
64
|
+
'rating': 'INTEGER',
|
|
65
|
+
'range': 'DECIMAL',
|
|
66
|
+
'doublerange': 'DECIMAL[]',
|
|
67
|
+
'markdown': 'TEXT',
|
|
68
|
+
'richtext': 'TEXT',
|
|
69
|
+
'code': 'TEXT',
|
|
70
|
+
'timezone': 'VARCHAR(100)',
|
|
71
|
+
'currency': 'VARCHAR(10)',
|
|
72
|
+
'country': 'VARCHAR(10)',
|
|
73
|
+
'address': 'JSONB',
|
|
74
|
+
'file': 'JSONB',
|
|
75
|
+
'image': 'JSONB',
|
|
76
|
+
'video': 'JSONB',
|
|
77
|
+
'audio': 'JSONB',
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def parse_fields_file(fields_path: Path) -> List[Dict]:
|
|
82
|
+
"""Parse TypeScript fields file to extract field definitions."""
|
|
83
|
+
fields = []
|
|
84
|
+
|
|
85
|
+
if not fields_path.exists():
|
|
86
|
+
print(f"Warning: Fields file not found: {fields_path}")
|
|
87
|
+
return fields
|
|
88
|
+
|
|
89
|
+
with open(fields_path, 'r', encoding='utf-8') as f:
|
|
90
|
+
content = f.read()
|
|
91
|
+
|
|
92
|
+
# Simple regex-based parser for field objects
|
|
93
|
+
import re
|
|
94
|
+
|
|
95
|
+
# Find field blocks
|
|
96
|
+
field_pattern = re.compile(r'\{[^}]*name:\s*[\'"](\w+)[\'"][^}]*type:\s*[\'"](\w+(?:-\w+)?)[\'"][^}]*required:\s*(true|false)[^}]*\}', re.DOTALL)
|
|
97
|
+
|
|
98
|
+
for match in field_pattern.finditer(content):
|
|
99
|
+
name = match.group(1)
|
|
100
|
+
field_type = match.group(2)
|
|
101
|
+
required = match.group(3) == 'true'
|
|
102
|
+
|
|
103
|
+
# Skip system fields
|
|
104
|
+
if name in ['id', 'createdAt', 'updatedAt', 'created_at', 'updated_at', 'userId', 'user_id']:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
fields.append({
|
|
108
|
+
'name': name,
|
|
109
|
+
'type': field_type,
|
|
110
|
+
'required': required
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
# Try to extract options for select fields
|
|
114
|
+
if field_type == 'select':
|
|
115
|
+
options_pattern = re.compile(rf'name:\s*[\'"]{name}[\'"][^}}]*options:\s*\[(.*?)\]', re.DOTALL)
|
|
116
|
+
options_match = options_pattern.search(content)
|
|
117
|
+
if options_match:
|
|
118
|
+
options_str = options_match.group(1)
|
|
119
|
+
values = re.findall(r'value:\s*[\'"](\w+)[\'"]', options_str)
|
|
120
|
+
fields[-1]['options'] = values
|
|
121
|
+
|
|
122
|
+
return fields
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def generate_column_sql(field: Dict) -> str:
|
|
126
|
+
"""Generate SQL column definition from field."""
|
|
127
|
+
name = to_snake_case(field['name'])
|
|
128
|
+
field_type = field['type']
|
|
129
|
+
required = field['required']
|
|
130
|
+
|
|
131
|
+
sql_type = FIELD_TYPE_MAP.get(field_type, 'TEXT')
|
|
132
|
+
|
|
133
|
+
# Handle select fields with CHECK constraint
|
|
134
|
+
if field_type == 'select' and 'options' in field:
|
|
135
|
+
options = ', '.join(f"'{v}'" for v in field['options'])
|
|
136
|
+
sql_type = f"VARCHAR(100) CHECK ({name} IN ({options}))"
|
|
137
|
+
|
|
138
|
+
nullable = '' if required else ''
|
|
139
|
+
not_null = ' NOT NULL' if required else ''
|
|
140
|
+
|
|
141
|
+
return f' "{name}" {sql_type}{not_null}'
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def generate_table_migration(entity_slug: str, fields: List[Dict], options: Dict) -> str:
|
|
145
|
+
"""Generate CREATE TABLE migration."""
|
|
146
|
+
table_name = to_snake_case(entity_slug)
|
|
147
|
+
timestamp = datetime.now().isoformat()
|
|
148
|
+
|
|
149
|
+
# Build column definitions
|
|
150
|
+
columns = [
|
|
151
|
+
' "id" UUID PRIMARY KEY DEFAULT gen_random_uuid()',
|
|
152
|
+
' "user_id" UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE',
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
for field in fields:
|
|
156
|
+
columns.append(generate_column_sql(field))
|
|
157
|
+
|
|
158
|
+
# Timestamps
|
|
159
|
+
columns.extend([
|
|
160
|
+
' "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP',
|
|
161
|
+
' "updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP',
|
|
162
|
+
])
|
|
163
|
+
|
|
164
|
+
# Soft delete
|
|
165
|
+
if options.get('soft_delete'):
|
|
166
|
+
columns.extend([
|
|
167
|
+
' "deleted_at" TIMESTAMPTZ',
|
|
168
|
+
' "deleted_by" UUID REFERENCES "user"(id)',
|
|
169
|
+
])
|
|
170
|
+
|
|
171
|
+
# Build SQL
|
|
172
|
+
sql = f'''-- Migration: Create {entity_slug} table
|
|
173
|
+
-- Description: Create {entity_slug} entity with fields from config
|
|
174
|
+
-- Generated: {timestamp}
|
|
175
|
+
|
|
176
|
+
-- Up Migration
|
|
177
|
+
BEGIN;
|
|
178
|
+
|
|
179
|
+
-- Create {table_name} table
|
|
180
|
+
CREATE TABLE "{table_name}" (
|
|
181
|
+
{','.join(columns)}
|
|
182
|
+
);
|
|
183
|
+
'''
|
|
184
|
+
|
|
185
|
+
# Indexes
|
|
186
|
+
if options.get('with_indexes', True):
|
|
187
|
+
sql += f'''
|
|
188
|
+
-- Indexes
|
|
189
|
+
CREATE INDEX "idx_{table_name}_user_id" ON "{table_name}" ("user_id");
|
|
190
|
+
CREATE INDEX "idx_{table_name}_created_at" ON "{table_name}" ("created_at" DESC);
|
|
191
|
+
'''
|
|
192
|
+
# Add indexes for searchable/sortable fields
|
|
193
|
+
for field in fields:
|
|
194
|
+
name = to_snake_case(field['name'])
|
|
195
|
+
if field['type'] in ['text', 'textarea']:
|
|
196
|
+
sql += f'CREATE INDEX "idx_{table_name}_{name}_search" ON "{table_name}" USING gin(to_tsvector(\'english\', {name}));\n'
|
|
197
|
+
elif field['type'] == 'select':
|
|
198
|
+
sql += f'CREATE INDEX "idx_{table_name}_{name}" ON "{table_name}" ("{name}");\n'
|
|
199
|
+
|
|
200
|
+
if options.get('soft_delete'):
|
|
201
|
+
sql += f'CREATE INDEX "idx_{table_name}_deleted_at" ON "{table_name}" ("deleted_at");\n'
|
|
202
|
+
|
|
203
|
+
# RLS
|
|
204
|
+
if options.get('with_rls', True):
|
|
205
|
+
sql += f'''
|
|
206
|
+
-- Enable Row Level Security
|
|
207
|
+
ALTER TABLE "{table_name}" ENABLE ROW LEVEL SECURITY;
|
|
208
|
+
|
|
209
|
+
-- RLS Policies
|
|
210
|
+
CREATE POLICY "{table_name}_owner_access" ON "{table_name}"
|
|
211
|
+
FOR ALL
|
|
212
|
+
USING (auth.uid() = user_id);
|
|
213
|
+
|
|
214
|
+
CREATE POLICY "{table_name}_service_role_access" ON "{table_name}"
|
|
215
|
+
FOR ALL TO service_role
|
|
216
|
+
USING (TRUE);
|
|
217
|
+
'''
|
|
218
|
+
|
|
219
|
+
sql += f'''
|
|
220
|
+
-- Trigger for updated_at
|
|
221
|
+
CREATE TRIGGER update_{table_name}_updated_at
|
|
222
|
+
BEFORE UPDATE ON "{table_name}"
|
|
223
|
+
FOR EACH ROW
|
|
224
|
+
EXECUTE FUNCTION update_updated_at_column();
|
|
225
|
+
|
|
226
|
+
COMMIT;
|
|
227
|
+
|
|
228
|
+
-- Down Migration (commented for safety)
|
|
229
|
+
-- BEGIN;
|
|
230
|
+
-- DROP TABLE IF EXISTS "{table_name}" CASCADE;
|
|
231
|
+
-- COMMIT;
|
|
232
|
+
'''
|
|
233
|
+
|
|
234
|
+
return sql
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def generate_metas_migration(entity_slug: str) -> str:
|
|
238
|
+
"""Generate metadata table migration."""
|
|
239
|
+
table_name = to_snake_case(entity_slug)
|
|
240
|
+
timestamp = datetime.now().isoformat()
|
|
241
|
+
|
|
242
|
+
return f'''-- Migration: Create {entity_slug}_metas table
|
|
243
|
+
-- Description: Create metadata table for {entity_slug} entity
|
|
244
|
+
-- Generated: {timestamp}
|
|
245
|
+
|
|
246
|
+
-- Up Migration
|
|
247
|
+
BEGIN;
|
|
248
|
+
|
|
249
|
+
-- Create {table_name}_metas table
|
|
250
|
+
CREATE TABLE "{table_name}_metas" (
|
|
251
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
252
|
+
"entity_id" UUID NOT NULL REFERENCES "{table_name}"(id) ON DELETE CASCADE,
|
|
253
|
+
"key" VARCHAR(255) NOT NULL,
|
|
254
|
+
"value" JSONB,
|
|
255
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
256
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
257
|
+
UNIQUE("entity_id", "key")
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
-- Indexes
|
|
261
|
+
CREATE INDEX "idx_{table_name}_metas_entity_id" ON "{table_name}_metas" ("entity_id");
|
|
262
|
+
CREATE INDEX "idx_{table_name}_metas_key" ON "{table_name}_metas" ("key");
|
|
263
|
+
CREATE INDEX "idx_{table_name}_metas_value" ON "{table_name}_metas" USING gin("value");
|
|
264
|
+
|
|
265
|
+
-- Enable Row Level Security
|
|
266
|
+
ALTER TABLE "{table_name}_metas" ENABLE ROW LEVEL SECURITY;
|
|
267
|
+
|
|
268
|
+
-- RLS Policies
|
|
269
|
+
CREATE POLICY "{table_name}_metas_owner_access" ON "{table_name}_metas"
|
|
270
|
+
FOR ALL
|
|
271
|
+
USING (
|
|
272
|
+
EXISTS (
|
|
273
|
+
SELECT 1 FROM "{table_name}"
|
|
274
|
+
WHERE "{table_name}".id = entity_id
|
|
275
|
+
AND "{table_name}".user_id = auth.uid()
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
CREATE POLICY "{table_name}_metas_service_role_access" ON "{table_name}_metas"
|
|
280
|
+
FOR ALL TO service_role
|
|
281
|
+
USING (TRUE);
|
|
282
|
+
|
|
283
|
+
-- Trigger for updated_at
|
|
284
|
+
CREATE TRIGGER update_{table_name}_metas_updated_at
|
|
285
|
+
BEFORE UPDATE ON "{table_name}_metas"
|
|
286
|
+
FOR EACH ROW
|
|
287
|
+
EXECUTE FUNCTION update_updated_at_column();
|
|
288
|
+
|
|
289
|
+
COMMIT;
|
|
290
|
+
|
|
291
|
+
-- Down Migration
|
|
292
|
+
-- DROP TABLE IF EXISTS "{table_name}_metas" CASCADE;
|
|
293
|
+
'''
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def generate_index_migration(entity_slug: str, fields: List[Dict]) -> str:
|
|
297
|
+
"""Generate index-only migration."""
|
|
298
|
+
table_name = to_snake_case(entity_slug)
|
|
299
|
+
timestamp = datetime.now().isoformat()
|
|
300
|
+
|
|
301
|
+
sql = f'''-- Migration: Add indexes to {entity_slug}
|
|
302
|
+
-- Generated: {timestamp}
|
|
303
|
+
|
|
304
|
+
BEGIN;
|
|
305
|
+
|
|
306
|
+
'''
|
|
307
|
+
|
|
308
|
+
for field in fields:
|
|
309
|
+
name = to_snake_case(field['name'])
|
|
310
|
+
if field['type'] in ['text', 'textarea']:
|
|
311
|
+
sql += f'CREATE INDEX IF NOT EXISTS "idx_{table_name}_{name}_search" ON "{table_name}" USING gin(to_tsvector(\'english\', {name}));\n'
|
|
312
|
+
elif field['type'] in ['select', 'boolean', 'date', 'datetime']:
|
|
313
|
+
sql += f'CREATE INDEX IF NOT EXISTS "idx_{table_name}_{name}" ON "{table_name}" ("{name}");\n'
|
|
314
|
+
elif field['type'] in ['relation', 'reference', 'user']:
|
|
315
|
+
sql += f'CREATE INDEX IF NOT EXISTS "idx_{table_name}_{name}" ON "{table_name}" ("{name}");\n'
|
|
316
|
+
|
|
317
|
+
sql += '\nCOMMIT;\n'
|
|
318
|
+
|
|
319
|
+
return sql
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def main():
|
|
323
|
+
parser = argparse.ArgumentParser(description='Generate entity migration')
|
|
324
|
+
parser.add_argument('--entity', required=True, help='Entity name (kebab-case)')
|
|
325
|
+
parser.add_argument('--theme', default=None, help='Theme name')
|
|
326
|
+
parser.add_argument('--type', choices=['table', 'metas', 'index', 'rls'], default='table')
|
|
327
|
+
parser.add_argument('--output', help='Output file (default: stdout)')
|
|
328
|
+
parser.add_argument('--with-rls', action='store_true', default=True)
|
|
329
|
+
parser.add_argument('--with-indexes', action='store_true', default=True)
|
|
330
|
+
parser.add_argument('--soft-delete', action='store_true')
|
|
331
|
+
|
|
332
|
+
args = parser.parse_args()
|
|
333
|
+
|
|
334
|
+
theme = args.theme or get_active_theme()
|
|
335
|
+
entity_slug = args.entity.lower()
|
|
336
|
+
|
|
337
|
+
# Find fields file
|
|
338
|
+
fields_path = Path(f'contents/themes/{theme}/entities/{entity_slug}/{entity_slug}.fields.ts')
|
|
339
|
+
|
|
340
|
+
print(f"\nGenerating migration for: {entity_slug}")
|
|
341
|
+
print(f"Theme: {theme}")
|
|
342
|
+
print(f"Type: {args.type}")
|
|
343
|
+
print(f"Fields file: {fields_path}")
|
|
344
|
+
|
|
345
|
+
# Parse fields
|
|
346
|
+
fields = parse_fields_file(fields_path)
|
|
347
|
+
print(f"Found {len(fields)} fields")
|
|
348
|
+
|
|
349
|
+
# Generate migration
|
|
350
|
+
options = {
|
|
351
|
+
'with_rls': args.with_rls,
|
|
352
|
+
'with_indexes': args.with_indexes,
|
|
353
|
+
'soft_delete': args.soft_delete,
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if args.type == 'table':
|
|
357
|
+
sql = generate_table_migration(entity_slug, fields, options)
|
|
358
|
+
elif args.type == 'metas':
|
|
359
|
+
sql = generate_metas_migration(entity_slug)
|
|
360
|
+
elif args.type == 'index':
|
|
361
|
+
sql = generate_index_migration(entity_slug, fields)
|
|
362
|
+
else:
|
|
363
|
+
print(f"Unknown migration type: {args.type}")
|
|
364
|
+
return 1
|
|
365
|
+
|
|
366
|
+
# Output
|
|
367
|
+
if args.output:
|
|
368
|
+
with open(args.output, 'w', encoding='utf-8') as f:
|
|
369
|
+
f.write(sql)
|
|
370
|
+
print(f"\nMigration written to: {args.output}")
|
|
371
|
+
else:
|
|
372
|
+
print("\n" + "=" * 60)
|
|
373
|
+
print("GENERATED MIGRATION:")
|
|
374
|
+
print("=" * 60)
|
|
375
|
+
print(sql)
|
|
376
|
+
print("=" * 60)
|
|
377
|
+
|
|
378
|
+
return 0
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
if __name__ == '__main__':
|
|
382
|
+
sys.exit(main())
|