@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.
Files changed (272) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/claude/_docs/workflows-optimizations.md +359 -0
  4. package/claude/agents/api-tester.md +634 -0
  5. package/claude/agents/architecture-supervisor.md +1351 -0
  6. package/claude/agents/backend-developer.md +997 -0
  7. package/claude/agents/backend-validator.md +417 -0
  8. package/claude/agents/bdd-docs-writer.md +737 -0
  9. package/claude/agents/block-developer.md +677 -0
  10. package/claude/agents/code-reviewer.md +1432 -0
  11. package/claude/agents/db-developer.md +721 -0
  12. package/claude/agents/db-validator.md +407 -0
  13. package/claude/agents/demo-video-generator.md +493 -0
  14. package/claude/agents/documentation-writer.md +1268 -0
  15. package/claude/agents/frontend-developer.md +1234 -0
  16. package/claude/agents/frontend-validator.md +777 -0
  17. package/claude/agents/functional-validator.md +630 -0
  18. package/claude/agents/mock-analyst.md +387 -0
  19. package/claude/agents/product-manager.md +963 -0
  20. package/claude/agents/qa-automation.md +1762 -0
  21. package/claude/agents/release-manager.md +634 -0
  22. package/claude/agents/selectors-translator.md +262 -0
  23. package/claude/agents/unit-test-writer.md +785 -0
  24. package/claude/agents/visual-comparator.md +329 -0
  25. package/claude/agents/workflow-maintainer.md +352 -0
  26. package/claude/commands/do/README.md +88 -0
  27. package/claude/commands/do/create-api.md +64 -0
  28. package/claude/commands/do/create-entity.md +66 -0
  29. package/claude/commands/do/create-migration.md +64 -0
  30. package/claude/commands/do/create-plugin.md +56 -0
  31. package/claude/commands/do/create-theme.md +70 -0
  32. package/claude/commands/do/mock-data.md +67 -0
  33. package/claude/commands/do/reset-db.md +71 -0
  34. package/claude/commands/do/setup-scheduled-action.md +75 -0
  35. package/claude/commands/do/sync-code-review.md +117 -0
  36. package/claude/commands/do/update-selectors.md +112 -0
  37. package/claude/commands/do/use-skills.md +90 -0
  38. package/claude/commands/do/validate-blocks.md +69 -0
  39. package/claude/commands/how-to/README.md +261 -0
  40. package/claude/commands/how-to/add-metadata.md +692 -0
  41. package/claude/commands/how-to/add-taxonomies.md +806 -0
  42. package/claude/commands/how-to/add-translations.md +571 -0
  43. package/claude/commands/how-to/create-api.md +577 -0
  44. package/claude/commands/how-to/create-block.md +575 -0
  45. package/claude/commands/how-to/create-child-entities.md +771 -0
  46. package/claude/commands/how-to/create-entity.md +597 -0
  47. package/claude/commands/how-to/create-migrations.md +605 -0
  48. package/claude/commands/how-to/create-plugin.md +654 -0
  49. package/claude/commands/how-to/customize-app.md +481 -0
  50. package/claude/commands/how-to/customize-dashboard.md +553 -0
  51. package/claude/commands/how-to/customize-theme.md +438 -0
  52. package/claude/commands/how-to/define-features-flows.md +632 -0
  53. package/claude/commands/how-to/deploy.md +507 -0
  54. package/claude/commands/how-to/handle-file-uploads.md +746 -0
  55. package/claude/commands/how-to/implement-search.md +1001 -0
  56. package/claude/commands/how-to/install-plugins.md +352 -0
  57. package/claude/commands/how-to/manage-test-coverage.md +984 -0
  58. package/claude/commands/how-to/run-tests.md +400 -0
  59. package/claude/commands/how-to/set-app-languages.md +601 -0
  60. package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
  61. package/claude/commands/how-to/set-scheduled-actions.md +527 -0
  62. package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
  63. package/claude/commands/how-to/setup-authentication.md +388 -0
  64. package/claude/commands/how-to/setup-claude-code.md +440 -0
  65. package/claude/commands/how-to/setup-database.md +274 -0
  66. package/claude/commands/how-to/setup-email-providers.md +598 -0
  67. package/claude/commands/how-to/setup-mobile-dev.md +627 -0
  68. package/claude/commands/how-to/start.md +500 -0
  69. package/claude/commands/how-to/use-devtools.md +639 -0
  70. package/claude/commands/how-to/use-superadmin.md +622 -0
  71. package/claude/commands/session/README.md +193 -0
  72. package/claude/commands/session/block-create.md +190 -0
  73. package/claude/commands/session/block-list.md +203 -0
  74. package/claude/commands/session/block-update.md +192 -0
  75. package/claude/commands/session/block-validate.md +218 -0
  76. package/claude/commands/session/changelog.md +115 -0
  77. package/claude/commands/session/close.md +225 -0
  78. package/claude/commands/session/commit.md +174 -0
  79. package/claude/commands/session/db-entity.md +206 -0
  80. package/claude/commands/session/db-fix.md +212 -0
  81. package/claude/commands/session/db-sample.md +206 -0
  82. package/claude/commands/session/demo.md +178 -0
  83. package/claude/commands/session/doc-bdd.md +207 -0
  84. package/claude/commands/session/doc-feature.md +218 -0
  85. package/claude/commands/session/doc-read.md +225 -0
  86. package/claude/commands/session/execute.md +204 -0
  87. package/claude/commands/session/explain.md +202 -0
  88. package/claude/commands/session/fix-bug.md +210 -0
  89. package/claude/commands/session/fix-build.md +182 -0
  90. package/claude/commands/session/fix-test.md +189 -0
  91. package/claude/commands/session/pending.md +232 -0
  92. package/claude/commands/session/refine.md +188 -0
  93. package/claude/commands/session/resume.md +192 -0
  94. package/claude/commands/session/review.md +192 -0
  95. package/claude/commands/session/scope-change.md +181 -0
  96. package/claude/commands/session/start-blocks.md +347 -0
  97. package/claude/commands/session/start.md +604 -0
  98. package/claude/commands/session/status.md +169 -0
  99. package/claude/commands/session/test-fix.md +221 -0
  100. package/claude/commands/session/test-run.md +203 -0
  101. package/claude/commands/session/test-write.md +242 -0
  102. package/claude/commands/session/validate.md +162 -0
  103. package/claude/config/context.json +40 -0
  104. package/claude/config/github.json +69 -0
  105. package/claude/config/github.schema.json +106 -0
  106. package/claude/config/team.json +46 -0
  107. package/claude/config/team.schema.json +106 -0
  108. package/claude/config/workspace.json +43 -0
  109. package/claude/config/workspace.schema.json +75 -0
  110. package/claude/skills/README.md +228 -0
  111. package/claude/skills/accessibility/SKILL.md +573 -0
  112. package/claude/skills/api-bypass-layers/SKILL.md +550 -0
  113. package/claude/skills/asana-integration/SKILL.md +499 -0
  114. package/claude/skills/better-auth/SKILL.md +666 -0
  115. package/claude/skills/billing-subscriptions/SKILL.md +660 -0
  116. package/claude/skills/block-decision-matrix/SKILL.md +359 -0
  117. package/claude/skills/clickup-integration/SKILL.md +434 -0
  118. package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
  119. package/claude/skills/create-plugin/SKILL.md +425 -0
  120. package/claude/skills/create-theme/SKILL.md +331 -0
  121. package/claude/skills/cypress-api/SKILL.md +511 -0
  122. package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
  123. package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
  124. package/claude/skills/cypress-e2e/SKILL.md +526 -0
  125. package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
  126. package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
  127. package/claude/skills/cypress-selectors/SKILL.md +309 -0
  128. package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
  129. package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
  130. package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
  131. package/claude/skills/database-migrations/SKILL.md +335 -0
  132. package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
  133. package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
  134. package/claude/skills/design-system/SKILL.md +682 -0
  135. package/claude/skills/documentation/SKILL.md +540 -0
  136. package/claude/skills/entity-api/SKILL.md +482 -0
  137. package/claude/skills/entity-system/SKILL.md +635 -0
  138. package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
  139. package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
  140. package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
  141. package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
  142. package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
  143. package/claude/skills/github/SKILL.md +467 -0
  144. package/claude/skills/i18n-nextintl/SKILL.md +302 -0
  145. package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
  146. package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
  147. package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
  148. package/claude/skills/impact-analysis/SKILL.md +203 -0
  149. package/claude/skills/jest-unit/SKILL.md +306 -0
  150. package/claude/skills/jest-unit/references/component-testing.md +371 -0
  151. package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
  152. package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
  153. package/claude/skills/jira-integration/SKILL.md +539 -0
  154. package/claude/skills/media-library/SKILL.md +743 -0
  155. package/claude/skills/mock-analysis/SKILL.md +276 -0
  156. package/claude/skills/monorepo-architecture/SKILL.md +162 -0
  157. package/claude/skills/nextjs-api-development/SKILL.md +364 -0
  158. package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
  159. package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
  160. package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
  161. package/claude/skills/notion-integration/SKILL.md +641 -0
  162. package/claude/skills/npm-development-workflow/SKILL.md +480 -0
  163. package/claude/skills/page-builder-blocks/SKILL.md +530 -0
  164. package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
  165. package/claude/skills/permissions-system/SKILL.md +619 -0
  166. package/claude/skills/plugins/SKILL.md +340 -0
  167. package/claude/skills/plugins/references/plugin-templates.md +414 -0
  168. package/claude/skills/plugins/references/plugin-testing.md +353 -0
  169. package/claude/skills/plugins/references/plugin-types.md +198 -0
  170. package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
  171. package/claude/skills/pom-patterns/SKILL.md +452 -0
  172. package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
  173. package/claude/skills/rate-limiting/SKILL.md +342 -0
  174. package/claude/skills/react-best-practices/AGENTS.md +2410 -0
  175. package/claude/skills/react-best-practices/README.md +123 -0
  176. package/claude/skills/react-best-practices/SKILL.md +125 -0
  177. package/claude/skills/react-best-practices/metadata.json +15 -0
  178. package/claude/skills/react-best-practices/rules/_sections.md +46 -0
  179. package/claude/skills/react-best-practices/rules/_template.md +28 -0
  180. package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  181. package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
  182. package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
  183. package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
  184. package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
  185. package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
  186. package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  187. package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  188. package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  189. package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  190. package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  191. package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
  192. package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  193. package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
  194. package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
  195. package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  196. package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
  197. package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  198. package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  199. package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  200. package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  201. package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
  202. package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  203. package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
  204. package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  205. package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  206. package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  207. package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  208. package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
  209. package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  210. package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  211. package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  212. package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  213. package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  214. package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  215. package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  216. package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  217. package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  218. package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  219. package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  220. package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
  221. package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  222. package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  223. package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  224. package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
  225. package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
  226. package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
  227. package/claude/skills/react-patterns/SKILL.md +688 -0
  228. package/claude/skills/registry-system/SKILL.md +331 -0
  229. package/claude/skills/scheduled-actions/SKILL.md +671 -0
  230. package/claude/skills/scope-enforcement/SKILL.md +542 -0
  231. package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
  232. package/claude/skills/server-actions/SKILL.md +493 -0
  233. package/claude/skills/service-layer/SKILL.md +587 -0
  234. package/claude/skills/session-management/SKILL.md +266 -0
  235. package/claude/skills/session-management/scripts/create-session.py +166 -0
  236. package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
  237. package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
  238. package/claude/skills/session-management/scripts/session-archive.sh +87 -0
  239. package/claude/skills/session-management/scripts/session-close.sh +133 -0
  240. package/claude/skills/session-management/scripts/session-init.sh +225 -0
  241. package/claude/skills/session-management/scripts/session-list.sh +163 -0
  242. package/claude/skills/session-management/scripts/split-plan.sh +116 -0
  243. package/claude/skills/shadcn-components/SKILL.md +586 -0
  244. package/claude/skills/shadcn-theming/SKILL.md +446 -0
  245. package/claude/skills/suspense-loading/SKILL.md +280 -0
  246. package/claude/skills/tailwind-theming/SKILL.md +507 -0
  247. package/claude/skills/tanstack-query/SKILL.md +608 -0
  248. package/claude/skills/test-coverage/SKILL.md +239 -0
  249. package/claude/skills/web-design-guidelines/SKILL.md +39 -0
  250. package/claude/skills/zod-validation/SKILL.md +537 -0
  251. package/claude/templates/blocks/progress.md +86 -0
  252. package/claude/templates/iteration/changes.md +61 -0
  253. package/claude/templates/iteration/progress.md +55 -0
  254. package/claude/templates/log.md +31 -0
  255. package/claude/templates/story/context.md +77 -0
  256. package/claude/templates/story/pendings.md +37 -0
  257. package/claude/templates/story/plan.md +299 -0
  258. package/claude/templates/story/requirements.md +109 -0
  259. package/claude/templates/story/scope.json +10 -0
  260. package/claude/templates/story/tests.md +91 -0
  261. package/claude/templates/task/progress.md +58 -0
  262. package/claude/templates/task/requirements.md +54 -0
  263. package/claude/workflows/README.md +154 -0
  264. package/claude/workflows/blocks.md +614 -0
  265. package/claude/workflows/story.md +1207 -0
  266. package/claude/workflows/task.md +927 -0
  267. package/claude/workflows/tweak.md +527 -0
  268. package/cursor/.gitkeep +0 -0
  269. package/package.json +35 -0
  270. package/scripts/postinstall.mjs +198 -0
  271. package/scripts/setup.mjs +282 -0
  272. package/scripts/sync.mjs +209 -0
@@ -0,0 +1,298 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate Child Entity Migration Script
4
+
5
+ Generates SQL migration for child entity table with parent reference.
6
+
7
+ Usage:
8
+ python generate-child-migration.py --parent PARENT --child CHILD [--theme THEME]
9
+
10
+ Options:
11
+ --parent PARENT Parent entity name (kebab-case, e.g., 'orders')
12
+ --child CHILD Child entity name (kebab-case, e.g., 'items')
13
+ --theme THEME Theme name (default: from NEXT_PUBLIC_ACTIVE_THEME or 'default')
14
+ --output FILE Output file path
15
+ --fields FIELDS Comma-separated field:type pairs (e.g., 'name:text,quantity:number')
16
+ --with-rls Include RLS policies (default: true)
17
+ --no-rls Exclude RLS policies
18
+ --dry-run Print SQL without writing file
19
+ """
20
+
21
+ import os
22
+ import sys
23
+ import argparse
24
+ import re
25
+ from pathlib import Path
26
+ from datetime import datetime
27
+ from typing import List, Dict, Tuple
28
+
29
+
30
+ def get_active_theme() -> str:
31
+ """Get active theme from environment or default."""
32
+ return os.environ.get('NEXT_PUBLIC_ACTIVE_THEME', 'default')
33
+
34
+
35
+ def to_snake_case(name: str) -> str:
36
+ """Convert kebab-case to snake_case."""
37
+ return name.replace('-', '_')
38
+
39
+
40
+ def to_camel_case(name: str) -> str:
41
+ """Convert kebab-case to camelCase."""
42
+ components = name.split('-')
43
+ return components[0] + ''.join(x.title() for x in components[1:])
44
+
45
+
46
+ # Field type to SQL type mapping
47
+ FIELD_TYPE_MAP = {
48
+ 'text': 'VARCHAR(255)',
49
+ 'textarea': 'TEXT',
50
+ 'number': 'DECIMAL(10, 2)',
51
+ 'integer': 'INTEGER',
52
+ 'boolean': 'BOOLEAN DEFAULT FALSE',
53
+ 'date': 'DATE',
54
+ 'datetime': 'TIMESTAMPTZ',
55
+ 'email': 'VARCHAR(255)',
56
+ 'url': 'TEXT',
57
+ 'json': 'JSONB DEFAULT \'{}\'::jsonb',
58
+ 'select': 'VARCHAR(100)',
59
+ 'uuid': 'TEXT',
60
+ }
61
+
62
+
63
+ def parse_fields(fields_str: str) -> List[Dict]:
64
+ """Parse comma-separated field:type pairs."""
65
+ if not fields_str:
66
+ return []
67
+
68
+ fields = []
69
+ for field_def in fields_str.split(','):
70
+ parts = field_def.strip().split(':')
71
+ if len(parts) >= 2:
72
+ name = parts[0].strip()
73
+ field_type = parts[1].strip()
74
+ required = len(parts) > 2 and parts[2].strip().lower() == 'required'
75
+ fields.append({
76
+ 'name': name,
77
+ 'type': field_type,
78
+ 'required': required
79
+ })
80
+ return fields
81
+
82
+
83
+ def to_pascal_case(name: str) -> str:
84
+ """Convert kebab-case to PascalCase."""
85
+ return ''.join(x.title() for x in name.split('-'))
86
+
87
+
88
+ def generate_child_migration(
89
+ parent_slug: str,
90
+ child_slug: str,
91
+ fields: List[Dict],
92
+ with_rls: bool = True
93
+ ) -> str:
94
+ """Generate child entity table migration SQL matching project conventions."""
95
+
96
+ parent_table = to_snake_case(parent_slug)
97
+ child_table = f"{parent_table}_{to_snake_case(child_slug)}"
98
+ parent_pascal = to_pascal_case(parent_slug)
99
+ child_pascal = to_pascal_case(child_slug)
100
+ date_str = datetime.now().strftime('%Y-%m-%d')
101
+
102
+ # Build column definitions with proper alignment
103
+ columns = [
104
+ ' -- Primary Key',
105
+ ' id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,',
106
+ '',
107
+ ' -- Parent Reference',
108
+ f' "parentId" TEXT NOT NULL REFERENCES public."{parent_table}"(id) ON DELETE CASCADE,',
109
+ ]
110
+
111
+ # Add custom fields if any
112
+ if fields:
113
+ columns.append('')
114
+ columns.append(' -- Entity-specific fields')
115
+ for field in fields:
116
+ name = field['name']
117
+ field_type = field['type']
118
+ required = field.get('required', False)
119
+
120
+ sql_type = FIELD_TYPE_MAP.get(field_type, 'TEXT')
121
+ not_null = ' NOT NULL' if required else ''
122
+ # Pad the name for alignment
123
+ padded_name = f'"{name}"'.ljust(14) if name[0].isupper() or '-' in name else name.ljust(14)
124
+ columns.append(f' {padded_name} {sql_type}{not_null},')
125
+
126
+ # System fields
127
+ columns.append('')
128
+ columns.append(' -- System fields')
129
+ columns.append(' "createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),')
130
+ columns.append(' "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now()')
131
+
132
+ columns_sql = '\n'.join(columns)
133
+
134
+ sql = f'''-- Migration: 003_{child_table}.sql
135
+ -- Description: {parent_pascal} {child_pascal} child entity (table, indexes, RLS)
136
+ -- Date: {date_str}
137
+
138
+ -- ============================================
139
+ -- TABLE
140
+ -- ============================================
141
+ CREATE TABLE IF NOT EXISTS public."{child_table}" (
142
+ {columns_sql}
143
+ );
144
+
145
+ COMMENT ON TABLE public."{child_table}" IS 'Child entity: {child_slug} for {parent_slug}';
146
+ COMMENT ON COLUMN public."{child_table}"."parentId" IS 'Reference to parent {parent_slug}';
147
+
148
+ -- ============================================
149
+ -- TRIGGER updatedAt
150
+ -- ============================================
151
+ DROP TRIGGER IF EXISTS {child_table}_set_updated_at ON public."{child_table}";
152
+ CREATE TRIGGER {child_table}_set_updated_at
153
+ BEFORE UPDATE ON public."{child_table}"
154
+ FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
155
+
156
+ -- ============================================
157
+ -- INDEXES
158
+ -- ============================================
159
+ CREATE INDEX IF NOT EXISTS idx_{child_table}_parent_id ON public."{child_table}"("parentId");
160
+ CREATE INDEX IF NOT EXISTS idx_{child_table}_created_at ON public."{child_table}"("createdAt" DESC);
161
+ '''
162
+
163
+ if with_rls:
164
+ sql += f'''
165
+ -- ============================================
166
+ -- RLS
167
+ -- ============================================
168
+ ALTER TABLE public."{child_table}" ENABLE ROW LEVEL SECURITY;
169
+
170
+ -- Cleanup existing policies
171
+ DROP POLICY IF EXISTS "{parent_pascal} {child_pascal} team can do all" ON public."{child_table}";
172
+
173
+ -- ============================
174
+ -- RLS: TEAM ISOLATION VIA PARENT
175
+ -- ============================
176
+ -- Hereda el aislamiento del parent via teamId
177
+ CREATE POLICY "{parent_pascal} {child_pascal} team can do all"
178
+ ON public."{child_table}"
179
+ FOR ALL TO authenticated
180
+ USING (
181
+ -- Superadmin bypass
182
+ public.is_superadmin()
183
+ OR
184
+ -- Team isolation via parent
185
+ EXISTS (
186
+ SELECT 1 FROM public."{parent_table}" t
187
+ WHERE t.id = "parentId"
188
+ AND t."teamId" = ANY(public.get_user_team_ids())
189
+ )
190
+ )
191
+ WITH CHECK (
192
+ public.is_superadmin()
193
+ OR
194
+ EXISTS (
195
+ SELECT 1 FROM public."{parent_table}" t
196
+ WHERE t.id = "parentId"
197
+ AND t."teamId" = ANY(public.get_user_team_ids())
198
+ )
199
+ );
200
+ '''
201
+
202
+ return sql
203
+
204
+
205
+ def find_next_migration_number(migrations_dir: Path) -> int:
206
+ """Find the next available migration number."""
207
+ if not migrations_dir.exists():
208
+ return 1
209
+
210
+ existing = list(migrations_dir.glob('*.sql'))
211
+ if not existing:
212
+ return 1
213
+
214
+ numbers = []
215
+ for f in existing:
216
+ try:
217
+ num = int(f.name.split('_')[0])
218
+ numbers.append(num)
219
+ except (ValueError, IndexError):
220
+ continue
221
+
222
+ return max(numbers, default=0) + 1
223
+
224
+
225
+ def main():
226
+ parser = argparse.ArgumentParser(description='Generate child entity migration')
227
+ parser.add_argument('--parent', required=True, help='Parent entity name (kebab-case)')
228
+ parser.add_argument('--child', required=True, help='Child entity name (kebab-case)')
229
+ parser.add_argument('--theme', default=None, help='Theme name')
230
+ parser.add_argument('--output', help='Output file path')
231
+ parser.add_argument('--fields', help='Comma-separated field:type pairs')
232
+ parser.add_argument('--with-rls', action='store_true', default=True)
233
+ parser.add_argument('--no-rls', action='store_true', help='Exclude RLS policies')
234
+ parser.add_argument('--dry-run', action='store_true', help='Print SQL without writing')
235
+
236
+ args = parser.parse_args()
237
+
238
+ theme = args.theme or get_active_theme()
239
+ parent_slug = args.parent.lower()
240
+ child_slug = args.child.lower()
241
+ with_rls = not args.no_rls
242
+
243
+ # Parse fields
244
+ fields = parse_fields(args.fields) if args.fields else []
245
+
246
+ print(f"\n{'='*60}")
247
+ print(f"Generating child entity migration")
248
+ print(f"Parent: {parent_slug}")
249
+ print(f"Child: {child_slug}")
250
+ print(f"Theme: {theme}")
251
+ print(f"With RLS: {with_rls}")
252
+ print(f"Fields: {len(fields)}")
253
+ print(f"{'='*60}")
254
+
255
+ # Generate SQL
256
+ sql = generate_child_migration(parent_slug, child_slug, fields, with_rls)
257
+
258
+ if args.dry_run:
259
+ print("\n" + sql)
260
+ print(f"\n{'='*60}")
261
+ print("DRY RUN - No file written")
262
+ return 0
263
+
264
+ # Determine output path
265
+ if args.output:
266
+ output_path = Path(args.output)
267
+ else:
268
+ # Auto-generate path in parent entity's migrations folder
269
+ entity_dir = Path(f'contents/themes/{theme}/entities/{parent_slug}')
270
+ migrations_dir = entity_dir / 'migrations'
271
+
272
+ if not entity_dir.exists():
273
+ print(f"\nError: Parent entity directory not found: {entity_dir}")
274
+ print("Please create the parent entity first using scaffold-entity.py")
275
+ return 1
276
+
277
+ migrations_dir.mkdir(exist_ok=True)
278
+
279
+ next_num = find_next_migration_number(migrations_dir)
280
+ parent_table = to_snake_case(parent_slug)
281
+ child_table = to_snake_case(child_slug)
282
+ output_path = migrations_dir / f'{next_num:03d}_{parent_table}_{child_table}.sql'
283
+
284
+ # Write file
285
+ with open(output_path, 'w', encoding='utf-8') as f:
286
+ f.write(sql)
287
+
288
+ print(f"\nMigration written to: {output_path}")
289
+ print(f"\nNext steps:")
290
+ print(f" 1. Add fields to the migration if not specified via --fields")
291
+ print(f" 2. Add childEntities config to {parent_slug}.config.ts")
292
+ print(f" 3. Run: pnpm db:migrate")
293
+
294
+ return 0
295
+
296
+
297
+ if __name__ == '__main__':
298
+ sys.exit(main())
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate Metas Migration Script
4
+
5
+ Generates SQL migration for entity metadata table.
6
+
7
+ Usage:
8
+ python generate-metas-migration.py --entity ENTITY_NAME [--theme THEME] [--output FILE]
9
+
10
+ Options:
11
+ --entity ENTITY_NAME Name of the entity (kebab-case, e.g., 'products', 'blog-posts')
12
+ --theme THEME Theme name (default: from NEXT_PUBLIC_ACTIVE_THEME or 'default')
13
+ --output FILE Output file (default: auto-generate path)
14
+ --with-rls Include RLS policies (default: true)
15
+ --dry-run Print SQL without writing file
16
+ """
17
+
18
+ import os
19
+ import sys
20
+ import argparse
21
+ from pathlib import Path
22
+ from datetime import datetime
23
+
24
+
25
+ def get_active_theme() -> str:
26
+ """Get active theme from environment or default."""
27
+ return os.environ.get('NEXT_PUBLIC_ACTIVE_THEME', 'default')
28
+
29
+
30
+ def to_camel_case(name: str) -> str:
31
+ """Convert kebab-case to camelCase."""
32
+ components = name.split('-')
33
+ return components[0] + ''.join(x.title() for x in components[1:])
34
+
35
+
36
+ def to_singular(name: str) -> str:
37
+ """Simple pluralization - remove trailing 's' for common cases."""
38
+ if name.endswith('ies'):
39
+ return name[:-3] + 'y'
40
+ elif name.endswith('es') and not name.endswith('ses'):
41
+ return name[:-2]
42
+ elif name.endswith('s') and not name.endswith('ss'):
43
+ return name[:-1]
44
+ return name
45
+
46
+
47
+ def to_pascal_case(name: str) -> str:
48
+ """Convert kebab-case to PascalCase."""
49
+ return ''.join(x.title() for x in name.split('-'))
50
+
51
+
52
+ def generate_metas_migration(entity_slug: str, with_rls: bool = True) -> str:
53
+ """Generate metadata table migration SQL matching project conventions."""
54
+
55
+ # Entity naming
56
+ table_name = entity_slug.replace('-', '_')
57
+ singular = to_singular(entity_slug).replace('-', '_')
58
+ pascal = to_pascal_case(entity_slug)
59
+ date_str = datetime.now().strftime('%Y-%m-%d')
60
+
61
+ sql = f'''-- Migration: 002_{table_name}_metas.sql
62
+ -- Description: {pascal} metas (table, indexes, RLS)
63
+ -- Date: {date_str}
64
+
65
+ -- ============================================
66
+ -- TABLE
67
+ -- ============================================
68
+ CREATE TABLE IF NOT EXISTS public."{table_name}_metas" (
69
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
70
+ "entityId" TEXT NOT NULL REFERENCES public."{table_name}"(id) ON DELETE CASCADE,
71
+ "metaKey" TEXT NOT NULL,
72
+ "metaValue" JSONB NOT NULL DEFAULT '{{}}'::jsonb,
73
+ "dataType" TEXT DEFAULT 'json',
74
+ "isPublic" BOOLEAN NOT NULL DEFAULT false,
75
+ "isSearchable" BOOLEAN NOT NULL DEFAULT false,
76
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
77
+ "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
78
+ CONSTRAINT {table_name}_metas_unique_key UNIQUE ("entityId", "metaKey")
79
+ );
80
+
81
+ COMMENT ON TABLE public."{table_name}_metas" IS 'Key-value metadata for {table_name}';
82
+ COMMENT ON COLUMN public."{table_name}_metas"."entityId" IS 'Reference to parent {singular}';
83
+ COMMENT ON COLUMN public."{table_name}_metas"."metaKey" IS 'Metadata key identifier';
84
+ COMMENT ON COLUMN public."{table_name}_metas"."metaValue" IS 'Metadata value in JSONB format';
85
+ COMMENT ON COLUMN public."{table_name}_metas"."dataType" IS 'Type hint: json, string, number, boolean';
86
+ COMMENT ON COLUMN public."{table_name}_metas"."isPublic" IS 'Whether this metadata is publicly readable';
87
+ COMMENT ON COLUMN public."{table_name}_metas"."isSearchable" IS 'Whether this metadata is searchable';
88
+
89
+ -- ============================================
90
+ -- TRIGGER updatedAt
91
+ -- ============================================
92
+ DROP TRIGGER IF EXISTS {table_name}_metas_set_updated_at ON public."{table_name}_metas";
93
+ CREATE TRIGGER {table_name}_metas_set_updated_at
94
+ BEFORE UPDATE ON public."{table_name}_metas"
95
+ FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
96
+
97
+ -- ============================================
98
+ -- INDEXES
99
+ -- ============================================
100
+ CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_entity_id ON public."{table_name}_metas"("entityId");
101
+ CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_key ON public."{table_name}_metas"("metaKey");
102
+ CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_is_public ON public."{table_name}_metas"("isPublic") WHERE "isPublic" = true;
103
+ CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_searchable ON public."{table_name}_metas"("isSearchable") WHERE "isSearchable" = true;
104
+ CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_value_gin ON public."{table_name}_metas" USING GIN ("metaValue");
105
+ CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_value_ops ON public."{table_name}_metas" USING GIN ("metaValue" jsonb_path_ops);
106
+ '''
107
+
108
+ if with_rls:
109
+ sql += f'''
110
+ -- ============================================
111
+ -- RLS
112
+ -- ============================================
113
+ ALTER TABLE public."{table_name}_metas" ENABLE ROW LEVEL SECURITY;
114
+
115
+ -- Cleanup existing policies
116
+ DROP POLICY IF EXISTS "{pascal} metas team can do all" ON public."{table_name}_metas";
117
+
118
+ -- ============================
119
+ -- RLS: TEAM ISOLATION VIA PARENT
120
+ -- ============================
121
+ -- Hereda el aislamiento del parent via teamId
122
+ CREATE POLICY "{pascal} metas team can do all"
123
+ ON public."{table_name}_metas"
124
+ FOR ALL TO authenticated
125
+ USING (
126
+ -- Superadmin bypass
127
+ public.is_superadmin()
128
+ OR
129
+ -- Team isolation via parent
130
+ EXISTS (
131
+ SELECT 1 FROM public."{table_name}" t
132
+ WHERE t.id = "entityId"
133
+ AND t."teamId" = ANY(public.get_user_team_ids())
134
+ )
135
+ )
136
+ WITH CHECK (
137
+ public.is_superadmin()
138
+ OR
139
+ EXISTS (
140
+ SELECT 1 FROM public."{table_name}" t
141
+ WHERE t.id = "entityId"
142
+ AND t."teamId" = ANY(public.get_user_team_ids())
143
+ )
144
+ );
145
+ '''
146
+
147
+ return sql
148
+
149
+
150
+ def find_next_migration_number(migrations_dir: Path) -> int:
151
+ """Find the next available migration number."""
152
+ if not migrations_dir.exists():
153
+ return 1
154
+
155
+ existing = list(migrations_dir.glob('*.sql'))
156
+ if not existing:
157
+ return 1
158
+
159
+ numbers = []
160
+ for f in existing:
161
+ try:
162
+ num = int(f.name.split('_')[0])
163
+ numbers.append(num)
164
+ except (ValueError, IndexError):
165
+ continue
166
+
167
+ return max(numbers, default=0) + 1
168
+
169
+
170
+ def main():
171
+ parser = argparse.ArgumentParser(description='Generate entity metas migration')
172
+ parser.add_argument('--entity', required=True, help='Entity name (kebab-case)')
173
+ parser.add_argument('--theme', default=None, help='Theme name')
174
+ parser.add_argument('--output', help='Output file path')
175
+ parser.add_argument('--with-rls', action='store_true', default=True, help='Include RLS policies')
176
+ parser.add_argument('--no-rls', action='store_true', help='Exclude RLS policies')
177
+ parser.add_argument('--dry-run', action='store_true', help='Print SQL without writing')
178
+
179
+ args = parser.parse_args()
180
+
181
+ theme = args.theme or get_active_theme()
182
+ entity_slug = args.entity.lower()
183
+ with_rls = not args.no_rls
184
+
185
+ print(f"\n{'='*60}")
186
+ print(f"Generating metas migration for: {entity_slug}")
187
+ print(f"Theme: {theme}")
188
+ print(f"With RLS: {with_rls}")
189
+ print(f"{'='*60}")
190
+
191
+ # Generate SQL
192
+ sql = generate_metas_migration(entity_slug, with_rls)
193
+
194
+ if args.dry_run:
195
+ print("\n" + sql)
196
+ print(f"\n{'='*60}")
197
+ print("DRY RUN - No file written")
198
+ return 0
199
+
200
+ # Determine output path
201
+ if args.output:
202
+ output_path = Path(args.output)
203
+ else:
204
+ # Auto-generate path in entity's migrations folder
205
+ entity_dir = Path(f'contents/themes/{theme}/entities/{entity_slug}')
206
+ migrations_dir = entity_dir / 'migrations'
207
+
208
+ if not entity_dir.exists():
209
+ print(f"\nError: Entity directory not found: {entity_dir}")
210
+ print("Please create the entity first using scaffold-entity.py")
211
+ return 1
212
+
213
+ migrations_dir.mkdir(exist_ok=True)
214
+
215
+ next_num = find_next_migration_number(migrations_dir)
216
+ table_name = entity_slug.replace('-', '_')
217
+ output_path = migrations_dir / f'{next_num:03d}_{table_name}_metas.sql'
218
+
219
+ # Write file
220
+ with open(output_path, 'w', encoding='utf-8') as f:
221
+ f.write(sql)
222
+
223
+ print(f"\nMigration written to: {output_path}")
224
+ print(f"\nNext steps:")
225
+ print(f" 1. Review the generated migration")
226
+ print(f" 2. Run: pnpm db:migrate")
227
+ print(f" 3. Update entity config: access.metadata = true")
228
+
229
+ return 0
230
+
231
+
232
+ if __name__ == '__main__':
233
+ sys.exit(main())