@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,661 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Scaffold Entity Script
|
|
4
|
+
|
|
5
|
+
Creates the complete file structure for a new entity.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python scaffold-entity.py --entity ENTITY_NAME [--theme THEME]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--entity ENTITY_NAME Name of the entity in kebab-case (e.g., blog-posts)
|
|
12
|
+
--theme THEME Theme name (default: from NEXT_PUBLIC_ACTIVE_THEME or 'default')
|
|
13
|
+
--with-metas Include metadata table migration
|
|
14
|
+
--with-builder Enable page builder for this entity
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
import argparse
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def to_camel_case(name: str) -> str:
|
|
26
|
+
"""Convert kebab-case to camelCase."""
|
|
27
|
+
components = name.split('-')
|
|
28
|
+
return components[0] + ''.join(x.title() for x in components[1:])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def to_pascal_case(name: str) -> str:
|
|
32
|
+
"""Convert kebab-case to PascalCase."""
|
|
33
|
+
return ''.join(x.title() for x in name.split('-'))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def to_snake_case(name: str) -> str:
|
|
37
|
+
"""Convert kebab-case to snake_case."""
|
|
38
|
+
return name.replace('-', '_')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def to_singular(name: str) -> str:
|
|
42
|
+
"""Convert plural to singular (simple heuristic)."""
|
|
43
|
+
if name.endswith('ies'):
|
|
44
|
+
return name[:-3] + 'y'
|
|
45
|
+
elif name.endswith('es') and name[-3] in 'sxz':
|
|
46
|
+
return name[:-2]
|
|
47
|
+
elif name.endswith('s') and not name.endswith('ss'):
|
|
48
|
+
return name[:-1]
|
|
49
|
+
return name
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_active_theme() -> str:
|
|
53
|
+
"""Get active theme from environment or default."""
|
|
54
|
+
return os.environ.get('NEXT_PUBLIC_ACTIVE_THEME', 'default')
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def generate_config_file(entity_slug: str, singular: str, pascal: str) -> str:
|
|
58
|
+
"""Generate entity config TypeScript file."""
|
|
59
|
+
return f'''/**
|
|
60
|
+
* {pascal} Entity Configuration
|
|
61
|
+
*
|
|
62
|
+
* Config-driven entity following the 5-section structure.
|
|
63
|
+
* All table names, API paths, and metadata are derived from slug.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
import {{ FileText }} from 'lucide-react'
|
|
67
|
+
import type {{ EntityConfig }} from '@/core/lib/entities/types'
|
|
68
|
+
import {{ {to_camel_case(entity_slug)}Fields }} from './{entity_slug}.fields'
|
|
69
|
+
|
|
70
|
+
export const {to_camel_case(singular)}EntityConfig: EntityConfig = {{
|
|
71
|
+
// ==========================================
|
|
72
|
+
// 1. BASIC IDENTIFICATION
|
|
73
|
+
// ==========================================
|
|
74
|
+
slug: '{entity_slug}',
|
|
75
|
+
enabled: true,
|
|
76
|
+
names: {{
|
|
77
|
+
singular: '{singular}',
|
|
78
|
+
plural: '{pascal}'
|
|
79
|
+
}},
|
|
80
|
+
icon: FileText,
|
|
81
|
+
|
|
82
|
+
// ==========================================
|
|
83
|
+
// 2. ACCESS AND SCOPE CONFIGURATION
|
|
84
|
+
// ==========================================
|
|
85
|
+
access: {{
|
|
86
|
+
public: false,
|
|
87
|
+
api: true,
|
|
88
|
+
metadata: false,
|
|
89
|
+
shared: false
|
|
90
|
+
}},
|
|
91
|
+
|
|
92
|
+
// ==========================================
|
|
93
|
+
// 3. UI/UX FEATURES
|
|
94
|
+
// ==========================================
|
|
95
|
+
ui: {{
|
|
96
|
+
dashboard: {{
|
|
97
|
+
showInMenu: true,
|
|
98
|
+
showInTopbar: true,
|
|
99
|
+
filters: [
|
|
100
|
+
// {{ field: 'status', type: 'multiSelect' }},
|
|
101
|
+
],
|
|
102
|
+
}},
|
|
103
|
+
public: {{
|
|
104
|
+
hasArchivePage: false,
|
|
105
|
+
hasSinglePage: false
|
|
106
|
+
}},
|
|
107
|
+
features: {{
|
|
108
|
+
searchable: true,
|
|
109
|
+
sortable: true,
|
|
110
|
+
filterable: true,
|
|
111
|
+
bulkOperations: true,
|
|
112
|
+
importExport: false
|
|
113
|
+
}}
|
|
114
|
+
}},
|
|
115
|
+
|
|
116
|
+
// ==========================================
|
|
117
|
+
// 4. PERMISSIONS SYSTEM
|
|
118
|
+
// ==========================================
|
|
119
|
+
// Permissions are centralized in permissions.config.ts
|
|
120
|
+
// See: contents/themes/{{theme}}/permissions.config.ts
|
|
121
|
+
|
|
122
|
+
// ==========================================
|
|
123
|
+
// 5. INTERNATIONALIZATION
|
|
124
|
+
// ==========================================
|
|
125
|
+
i18n: {{
|
|
126
|
+
fallbackLocale: 'en',
|
|
127
|
+
loaders: {{
|
|
128
|
+
es: () => import('./messages/es.json'),
|
|
129
|
+
en: () => import('./messages/en.json')
|
|
130
|
+
}}
|
|
131
|
+
}},
|
|
132
|
+
|
|
133
|
+
// ==========================================
|
|
134
|
+
// FIELDS (imported from separate file)
|
|
135
|
+
// ==========================================
|
|
136
|
+
fields: {to_camel_case(entity_slug)}Fields,
|
|
137
|
+
}}
|
|
138
|
+
'''
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def generate_fields_file(entity_slug: str, singular: str, pascal: str) -> str:
|
|
142
|
+
"""Generate entity fields TypeScript file."""
|
|
143
|
+
return f'''/**
|
|
144
|
+
* {pascal} Entity Fields Configuration
|
|
145
|
+
*
|
|
146
|
+
* Field definitions separated from main config.
|
|
147
|
+
* Contains all field definitions for the {entity_slug} entity.
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
import type {{ EntityField }} from '@/core/lib/entities/types'
|
|
151
|
+
|
|
152
|
+
export const {to_camel_case(entity_slug)}Fields: EntityField[] = [
|
|
153
|
+
{{
|
|
154
|
+
name: 'title',
|
|
155
|
+
type: 'text',
|
|
156
|
+
required: true,
|
|
157
|
+
display: {{
|
|
158
|
+
label: 'Title',
|
|
159
|
+
description: '{pascal} title',
|
|
160
|
+
placeholder: 'Enter title...',
|
|
161
|
+
showInList: true,
|
|
162
|
+
showInDetail: true,
|
|
163
|
+
showInForm: true,
|
|
164
|
+
order: 1,
|
|
165
|
+
columnWidth: 12,
|
|
166
|
+
}},
|
|
167
|
+
api: {{
|
|
168
|
+
readOnly: false,
|
|
169
|
+
searchable: true,
|
|
170
|
+
sortable: true,
|
|
171
|
+
}},
|
|
172
|
+
}},
|
|
173
|
+
{{
|
|
174
|
+
name: 'description',
|
|
175
|
+
type: 'textarea',
|
|
176
|
+
required: false,
|
|
177
|
+
display: {{
|
|
178
|
+
label: 'Description',
|
|
179
|
+
description: 'Detailed description',
|
|
180
|
+
placeholder: 'Enter description...',
|
|
181
|
+
showInList: false,
|
|
182
|
+
showInDetail: true,
|
|
183
|
+
showInForm: true,
|
|
184
|
+
order: 2,
|
|
185
|
+
columnWidth: 12,
|
|
186
|
+
}},
|
|
187
|
+
api: {{
|
|
188
|
+
readOnly: false,
|
|
189
|
+
searchable: true,
|
|
190
|
+
sortable: false,
|
|
191
|
+
}},
|
|
192
|
+
}},
|
|
193
|
+
{{
|
|
194
|
+
name: 'status',
|
|
195
|
+
type: 'select',
|
|
196
|
+
required: false,
|
|
197
|
+
defaultValue: 'draft',
|
|
198
|
+
options: [
|
|
199
|
+
{{ value: 'draft', label: 'Draft' }},
|
|
200
|
+
{{ value: 'active', label: 'Active' }},
|
|
201
|
+
{{ value: 'archived', label: 'Archived' }},
|
|
202
|
+
],
|
|
203
|
+
display: {{
|
|
204
|
+
label: 'Status',
|
|
205
|
+
description: 'Current status',
|
|
206
|
+
placeholder: 'Select status...',
|
|
207
|
+
showInList: true,
|
|
208
|
+
showInDetail: true,
|
|
209
|
+
showInForm: true,
|
|
210
|
+
order: 3,
|
|
211
|
+
columnWidth: 6,
|
|
212
|
+
}},
|
|
213
|
+
api: {{
|
|
214
|
+
readOnly: false,
|
|
215
|
+
searchable: false,
|
|
216
|
+
sortable: true,
|
|
217
|
+
}},
|
|
218
|
+
}},
|
|
219
|
+
// NOTE: id, createdAt, updatedAt are implicit system fields
|
|
220
|
+
// They are automatically included by the API and frontend
|
|
221
|
+
// Do NOT declare them here - see core/lib/entities/system-fields.ts
|
|
222
|
+
]
|
|
223
|
+
'''
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def generate_types_file(entity_slug: str, singular: str, pascal: str) -> str:
|
|
227
|
+
"""Generate entity types TypeScript file."""
|
|
228
|
+
return f'''/**
|
|
229
|
+
* {pascal} Types
|
|
230
|
+
*
|
|
231
|
+
* Type definitions for the {pascal} entity.
|
|
232
|
+
*
|
|
233
|
+
* @module {pascal}Types
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* {pascal} status values
|
|
238
|
+
*/
|
|
239
|
+
export type {pascal}Status = 'draft' | 'active' | 'archived'
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* {pascal} entity
|
|
243
|
+
*/
|
|
244
|
+
export interface {pascal} {{
|
|
245
|
+
id: string
|
|
246
|
+
userId: string
|
|
247
|
+
teamId: string
|
|
248
|
+
title: string
|
|
249
|
+
description?: string
|
|
250
|
+
status: {pascal}Status
|
|
251
|
+
createdAt: string
|
|
252
|
+
updatedAt: string
|
|
253
|
+
}}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Input for creating a {singular}
|
|
257
|
+
*/
|
|
258
|
+
export interface Create{pascal}Input {{
|
|
259
|
+
title: string
|
|
260
|
+
description?: string
|
|
261
|
+
status?: {pascal}Status
|
|
262
|
+
}}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Input for updating a {singular}
|
|
266
|
+
*/
|
|
267
|
+
export interface Update{pascal}Input {{
|
|
268
|
+
title?: string
|
|
269
|
+
description?: string
|
|
270
|
+
status?: {pascal}Status
|
|
271
|
+
}}
|
|
272
|
+
'''
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def generate_service_file(entity_slug: str, singular: str, pascal: str) -> str:
|
|
276
|
+
"""Generate entity service TypeScript file."""
|
|
277
|
+
table_name = entity_slug.replace('-', '_')
|
|
278
|
+
camel = to_camel_case(entity_slug)
|
|
279
|
+
return f'''/**
|
|
280
|
+
* {pascal} Service
|
|
281
|
+
*
|
|
282
|
+
* Provides data access methods for {entity_slug}.
|
|
283
|
+
* Extends BaseEntityService for standard CRUD operations.
|
|
284
|
+
*
|
|
285
|
+
* @module {pascal}Service
|
|
286
|
+
*/
|
|
287
|
+
|
|
288
|
+
import {{ BaseEntityService }} from '@/core/lib/services/base-entity.service'
|
|
289
|
+
import type {{ {pascal}, Create{pascal}Input, Update{pascal}Input }} from './{entity_slug}.types'
|
|
290
|
+
|
|
291
|
+
class {pascal}ServiceClass extends BaseEntityService<{pascal}, Create{pascal}Input, Update{pascal}Input> {{
|
|
292
|
+
constructor() {{
|
|
293
|
+
super({{
|
|
294
|
+
tableName: '{table_name}',
|
|
295
|
+
fields: ['title', 'description', 'status'],
|
|
296
|
+
searchableFields: ['title'],
|
|
297
|
+
defaultOrderBy: 'createdAt',
|
|
298
|
+
defaultOrderDir: 'desc',
|
|
299
|
+
}})
|
|
300
|
+
}}
|
|
301
|
+
|
|
302
|
+
// ============================================
|
|
303
|
+
// CUSTOM METHODS
|
|
304
|
+
// ============================================
|
|
305
|
+
// Add entity-specific methods here. Examples:
|
|
306
|
+
//
|
|
307
|
+
// async getByStatus(userId: string, status: string): Promise<{pascal}[]> {{
|
|
308
|
+
// return this.query(userId, {{ where: {{ status }} }})
|
|
309
|
+
// }}
|
|
310
|
+
//
|
|
311
|
+
// async getRecent(userId: string, limit: number = 5): Promise<{pascal}[]> {{
|
|
312
|
+
// return this.query(userId, {{ limit, orderBy: 'createdAt', orderDir: 'desc' }})
|
|
313
|
+
// }}
|
|
314
|
+
}}
|
|
315
|
+
|
|
316
|
+
// Export singleton instance
|
|
317
|
+
export const {camel}Service = new {pascal}ServiceClass()
|
|
318
|
+
|
|
319
|
+
// Export class for testing
|
|
320
|
+
export {{ {pascal}ServiceClass }}
|
|
321
|
+
'''
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def generate_messages_en(entity_slug: str, singular: str, pascal: str) -> str:
|
|
325
|
+
"""Generate English messages JSON."""
|
|
326
|
+
return f'''{{
|
|
327
|
+
"title": "{pascal}",
|
|
328
|
+
"description": "Manage your {entity_slug}",
|
|
329
|
+
"fields": {{
|
|
330
|
+
"title": {{
|
|
331
|
+
"label": "Title",
|
|
332
|
+
"placeholder": "Enter title...",
|
|
333
|
+
"description": "{pascal} title"
|
|
334
|
+
}},
|
|
335
|
+
"description": {{
|
|
336
|
+
"label": "Description",
|
|
337
|
+
"placeholder": "Enter description...",
|
|
338
|
+
"description": "Detailed description"
|
|
339
|
+
}},
|
|
340
|
+
"status": {{
|
|
341
|
+
"label": "Status",
|
|
342
|
+
"placeholder": "Select status...",
|
|
343
|
+
"options": {{
|
|
344
|
+
"draft": "Draft",
|
|
345
|
+
"active": "Active",
|
|
346
|
+
"archived": "Archived"
|
|
347
|
+
}}
|
|
348
|
+
}}
|
|
349
|
+
}},
|
|
350
|
+
"actions": {{
|
|
351
|
+
"create": "Create {singular.title()}",
|
|
352
|
+
"edit": "Edit {singular.title()}",
|
|
353
|
+
"delete": "Delete {singular.title()}",
|
|
354
|
+
"view": "View {singular.title()}"
|
|
355
|
+
}},
|
|
356
|
+
"messages": {{
|
|
357
|
+
"createSuccess": "{singular.title()} created successfully",
|
|
358
|
+
"updateSuccess": "{singular.title()} updated successfully",
|
|
359
|
+
"deleteSuccess": "{singular.title()} deleted successfully",
|
|
360
|
+
"deleteConfirm": "Are you sure you want to delete this {singular}?"
|
|
361
|
+
}}
|
|
362
|
+
}}
|
|
363
|
+
'''
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def generate_messages_es(entity_slug: str, singular: str, pascal: str) -> str:
|
|
367
|
+
"""Generate Spanish messages JSON."""
|
|
368
|
+
return f'''{{
|
|
369
|
+
"title": "{pascal}",
|
|
370
|
+
"description": "Gestiona tus {entity_slug}",
|
|
371
|
+
"fields": {{
|
|
372
|
+
"title": {{
|
|
373
|
+
"label": "Titulo",
|
|
374
|
+
"placeholder": "Ingrese titulo...",
|
|
375
|
+
"description": "Titulo del {singular}"
|
|
376
|
+
}},
|
|
377
|
+
"description": {{
|
|
378
|
+
"label": "Descripcion",
|
|
379
|
+
"placeholder": "Ingrese descripcion...",
|
|
380
|
+
"description": "Descripcion detallada"
|
|
381
|
+
}},
|
|
382
|
+
"status": {{
|
|
383
|
+
"label": "Estado",
|
|
384
|
+
"placeholder": "Seleccione estado...",
|
|
385
|
+
"options": {{
|
|
386
|
+
"draft": "Borrador",
|
|
387
|
+
"active": "Activo",
|
|
388
|
+
"archived": "Archivado"
|
|
389
|
+
}}
|
|
390
|
+
}}
|
|
391
|
+
}},
|
|
392
|
+
"actions": {{
|
|
393
|
+
"create": "Crear {singular}",
|
|
394
|
+
"edit": "Editar {singular}",
|
|
395
|
+
"delete": "Eliminar {singular}",
|
|
396
|
+
"view": "Ver {singular}"
|
|
397
|
+
}},
|
|
398
|
+
"messages": {{
|
|
399
|
+
"createSuccess": "{singular.title()} creado exitosamente",
|
|
400
|
+
"updateSuccess": "{singular.title()} actualizado exitosamente",
|
|
401
|
+
"deleteSuccess": "{singular.title()} eliminado exitosamente",
|
|
402
|
+
"deleteConfirm": "Esta seguro que desea eliminar este {singular}?"
|
|
403
|
+
}}
|
|
404
|
+
}}
|
|
405
|
+
'''
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def generate_migration(entity_slug: str, singular: str, pascal: str) -> str:
|
|
409
|
+
"""Generate migration SQL file matching project conventions."""
|
|
410
|
+
table_name = to_snake_case(entity_slug)
|
|
411
|
+
date_str = datetime.now().strftime('%Y-%m-%d')
|
|
412
|
+
|
|
413
|
+
return f'''-- Migration: 001_{table_name}_table.sql
|
|
414
|
+
-- Description: {pascal} (table, indexes, RLS)
|
|
415
|
+
-- Date: {date_str}
|
|
416
|
+
|
|
417
|
+
-- ============================================
|
|
418
|
+
-- TABLE
|
|
419
|
+
-- ============================================
|
|
420
|
+
CREATE TABLE IF NOT EXISTS public."{table_name}" (
|
|
421
|
+
-- Primary Key
|
|
422
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
423
|
+
|
|
424
|
+
-- Relational Fields
|
|
425
|
+
"userId" TEXT NOT NULL REFERENCES public."users"(id) ON DELETE CASCADE,
|
|
426
|
+
"teamId" TEXT NOT NULL REFERENCES public."teams"(id) ON DELETE CASCADE,
|
|
427
|
+
|
|
428
|
+
-- Entity-specific fields
|
|
429
|
+
title TEXT NOT NULL,
|
|
430
|
+
description TEXT,
|
|
431
|
+
status TEXT NOT NULL DEFAULT 'draft',
|
|
432
|
+
|
|
433
|
+
-- System fields
|
|
434
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
435
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
436
|
+
|
|
437
|
+
-- Constraints
|
|
438
|
+
CONSTRAINT {table_name}_status_check CHECK (status IN ('draft', 'active', 'archived'))
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
COMMENT ON TABLE public."{table_name}" IS '{pascal} table with team isolation via RLS';
|
|
442
|
+
COMMENT ON COLUMN public."{table_name}"."userId" IS 'Owner user id';
|
|
443
|
+
COMMENT ON COLUMN public."{table_name}"."teamId" IS 'Team context for isolation';
|
|
444
|
+
COMMENT ON COLUMN public."{table_name}".title IS '{singular.title()} title';
|
|
445
|
+
COMMENT ON COLUMN public."{table_name}".description IS 'Detailed description';
|
|
446
|
+
COMMENT ON COLUMN public."{table_name}".status IS 'Status: draft, active, archived';
|
|
447
|
+
|
|
448
|
+
-- ============================================
|
|
449
|
+
-- TRIGGER updatedAt
|
|
450
|
+
-- ============================================
|
|
451
|
+
DROP TRIGGER IF EXISTS {table_name}_set_updated_at ON public."{table_name}";
|
|
452
|
+
CREATE TRIGGER {table_name}_set_updated_at
|
|
453
|
+
BEFORE UPDATE ON public."{table_name}"
|
|
454
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
455
|
+
|
|
456
|
+
-- ============================================
|
|
457
|
+
-- INDEXES
|
|
458
|
+
-- ============================================
|
|
459
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_user_id ON public."{table_name}"("userId");
|
|
460
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_team_id ON public."{table_name}"("teamId");
|
|
461
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_user_team ON public."{table_name}"("userId", "teamId");
|
|
462
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_status ON public."{table_name}"(status);
|
|
463
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_created_at ON public."{table_name}"("createdAt" DESC);
|
|
464
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_title_search ON public."{table_name}" USING GIN(to_tsvector('english', title));
|
|
465
|
+
|
|
466
|
+
-- ============================================
|
|
467
|
+
-- RLS
|
|
468
|
+
-- ============================================
|
|
469
|
+
ALTER TABLE public."{table_name}" ENABLE ROW LEVEL SECURITY;
|
|
470
|
+
|
|
471
|
+
-- Cleanup existing policies
|
|
472
|
+
DROP POLICY IF EXISTS "{pascal} team can do all" ON public."{table_name}";
|
|
473
|
+
|
|
474
|
+
-- ============================
|
|
475
|
+
-- RLS: TEAM ISOLATION
|
|
476
|
+
-- ============================
|
|
477
|
+
-- RLS verifies team membership
|
|
478
|
+
-- The access.shared (user isolation) logic is handled at APP LEVEL
|
|
479
|
+
CREATE POLICY "{pascal} team can do all"
|
|
480
|
+
ON public."{table_name}"
|
|
481
|
+
FOR ALL TO authenticated
|
|
482
|
+
USING (
|
|
483
|
+
-- Superadmin bypass
|
|
484
|
+
public.is_superadmin()
|
|
485
|
+
OR
|
|
486
|
+
-- Team isolation: user must be member of the team
|
|
487
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
488
|
+
)
|
|
489
|
+
WITH CHECK (
|
|
490
|
+
public.is_superadmin()
|
|
491
|
+
OR
|
|
492
|
+
"teamId" = ANY(public.get_user_team_ids())
|
|
493
|
+
);
|
|
494
|
+
'''
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def generate_metas_migration(entity_slug: str, singular: str, pascal: str) -> str:
|
|
498
|
+
"""Generate metas table migration SQL file matching project conventions."""
|
|
499
|
+
table_name = to_snake_case(entity_slug)
|
|
500
|
+
date_str = datetime.now().strftime('%Y-%m-%d')
|
|
501
|
+
|
|
502
|
+
return f'''-- Migration: 002_{table_name}_metas.sql
|
|
503
|
+
-- Description: {pascal} metas (table, indexes, RLS)
|
|
504
|
+
-- Date: {date_str}
|
|
505
|
+
|
|
506
|
+
-- ============================================
|
|
507
|
+
-- TABLE
|
|
508
|
+
-- ============================================
|
|
509
|
+
CREATE TABLE IF NOT EXISTS public."{table_name}_metas" (
|
|
510
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
511
|
+
"entityId" TEXT NOT NULL REFERENCES public."{table_name}"(id) ON DELETE CASCADE,
|
|
512
|
+
"metaKey" TEXT NOT NULL,
|
|
513
|
+
"metaValue" JSONB NOT NULL DEFAULT '{{}}'::jsonb,
|
|
514
|
+
"dataType" TEXT DEFAULT 'json',
|
|
515
|
+
"isPublic" BOOLEAN NOT NULL DEFAULT false,
|
|
516
|
+
"isSearchable" BOOLEAN NOT NULL DEFAULT false,
|
|
517
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
518
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
519
|
+
CONSTRAINT {table_name}_metas_unique_key UNIQUE ("entityId", "metaKey")
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
COMMENT ON TABLE public."{table_name}_metas" IS 'Key-value metadata for {table_name}';
|
|
523
|
+
COMMENT ON COLUMN public."{table_name}_metas"."entityId" IS 'Reference to parent {singular}';
|
|
524
|
+
COMMENT ON COLUMN public."{table_name}_metas"."metaKey" IS 'Metadata key identifier';
|
|
525
|
+
COMMENT ON COLUMN public."{table_name}_metas"."metaValue" IS 'Metadata value in JSONB format';
|
|
526
|
+
COMMENT ON COLUMN public."{table_name}_metas"."dataType" IS 'Type hint: json, string, number, boolean';
|
|
527
|
+
COMMENT ON COLUMN public."{table_name}_metas"."isPublic" IS 'Whether this metadata is publicly readable';
|
|
528
|
+
COMMENT ON COLUMN public."{table_name}_metas"."isSearchable" IS 'Whether this metadata is searchable';
|
|
529
|
+
|
|
530
|
+
-- ============================================
|
|
531
|
+
-- TRIGGER updatedAt
|
|
532
|
+
-- ============================================
|
|
533
|
+
DROP TRIGGER IF EXISTS {table_name}_metas_set_updated_at ON public."{table_name}_metas";
|
|
534
|
+
CREATE TRIGGER {table_name}_metas_set_updated_at
|
|
535
|
+
BEFORE UPDATE ON public."{table_name}_metas"
|
|
536
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
537
|
+
|
|
538
|
+
-- ============================================
|
|
539
|
+
-- INDEXES
|
|
540
|
+
-- ============================================
|
|
541
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_entity_id ON public."{table_name}_metas"("entityId");
|
|
542
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_key ON public."{table_name}_metas"("metaKey");
|
|
543
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_is_public ON public."{table_name}_metas"("isPublic") WHERE "isPublic" = true;
|
|
544
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_searchable ON public."{table_name}_metas"("isSearchable") WHERE "isSearchable" = true;
|
|
545
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_value_gin ON public."{table_name}_metas" USING GIN ("metaValue");
|
|
546
|
+
CREATE INDEX IF NOT EXISTS idx_{table_name}_metas_value_ops ON public."{table_name}_metas" USING GIN ("metaValue" jsonb_path_ops);
|
|
547
|
+
|
|
548
|
+
-- ============================================
|
|
549
|
+
-- RLS
|
|
550
|
+
-- ============================================
|
|
551
|
+
ALTER TABLE public."{table_name}_metas" ENABLE ROW LEVEL SECURITY;
|
|
552
|
+
|
|
553
|
+
-- Cleanup existing policies
|
|
554
|
+
DROP POLICY IF EXISTS "{pascal} metas team can do all" ON public."{table_name}_metas";
|
|
555
|
+
|
|
556
|
+
-- ============================
|
|
557
|
+
-- RLS: TEAM ISOLATION VIA PARENT
|
|
558
|
+
-- ============================
|
|
559
|
+
-- Inherits isolation from parent via teamId
|
|
560
|
+
CREATE POLICY "{pascal} metas team can do all"
|
|
561
|
+
ON public."{table_name}_metas"
|
|
562
|
+
FOR ALL TO authenticated
|
|
563
|
+
USING (
|
|
564
|
+
-- Superadmin bypass
|
|
565
|
+
public.is_superadmin()
|
|
566
|
+
OR
|
|
567
|
+
-- Team isolation via parent
|
|
568
|
+
EXISTS (
|
|
569
|
+
SELECT 1 FROM public."{table_name}" t
|
|
570
|
+
WHERE t.id = "entityId"
|
|
571
|
+
AND t."teamId" = ANY(public.get_user_team_ids())
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
WITH CHECK (
|
|
575
|
+
public.is_superadmin()
|
|
576
|
+
OR
|
|
577
|
+
EXISTS (
|
|
578
|
+
SELECT 1 FROM public."{table_name}" t
|
|
579
|
+
WHERE t.id = "entityId"
|
|
580
|
+
AND t."teamId" = ANY(public.get_user_team_ids())
|
|
581
|
+
)
|
|
582
|
+
);
|
|
583
|
+
'''
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def main():
|
|
587
|
+
parser = argparse.ArgumentParser(description='Scaffold a new entity')
|
|
588
|
+
parser.add_argument('--entity', required=True, help='Entity name in kebab-case (plural)')
|
|
589
|
+
parser.add_argument('--theme', default=None, help='Theme name')
|
|
590
|
+
parser.add_argument('--with-metas', action='store_true', help='Include metadata table')
|
|
591
|
+
parser.add_argument('--with-builder', action='store_true', help='Enable page builder')
|
|
592
|
+
parser.add_argument('--dry-run', action='store_true', help='Show what would be created')
|
|
593
|
+
|
|
594
|
+
args = parser.parse_args()
|
|
595
|
+
|
|
596
|
+
theme = args.theme or get_active_theme()
|
|
597
|
+
entity_slug = args.entity.lower()
|
|
598
|
+
singular = to_singular(entity_slug)
|
|
599
|
+
pascal = to_pascal_case(entity_slug)
|
|
600
|
+
|
|
601
|
+
# Base path
|
|
602
|
+
base_path = Path(f'contents/themes/{theme}/entities/{entity_slug}')
|
|
603
|
+
|
|
604
|
+
print(f"\n{'=' * 60}")
|
|
605
|
+
print(f"SCAFFOLDING ENTITY: {entity_slug}")
|
|
606
|
+
print(f"{'=' * 60}")
|
|
607
|
+
print(f"Theme: {theme}")
|
|
608
|
+
print(f"Singular: {singular}")
|
|
609
|
+
print(f"Pascal: {pascal}")
|
|
610
|
+
print(f"Base path: {base_path}")
|
|
611
|
+
print(f"{'=' * 60}\n")
|
|
612
|
+
|
|
613
|
+
# Files to create
|
|
614
|
+
files = {
|
|
615
|
+
f'{entity_slug}.config.ts': generate_config_file(entity_slug, singular, pascal),
|
|
616
|
+
f'{entity_slug}.fields.ts': generate_fields_file(entity_slug, singular, pascal),
|
|
617
|
+
f'{entity_slug}.types.ts': generate_types_file(entity_slug, singular, pascal),
|
|
618
|
+
f'{entity_slug}.service.ts': generate_service_file(entity_slug, singular, pascal),
|
|
619
|
+
'messages/en.json': generate_messages_en(entity_slug, singular, pascal),
|
|
620
|
+
'messages/es.json': generate_messages_es(entity_slug, singular, pascal),
|
|
621
|
+
f'migrations/001_{to_snake_case(entity_slug)}_table.sql': generate_migration(entity_slug, singular, pascal),
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
# Add metas migration if requested
|
|
625
|
+
if args.with_metas:
|
|
626
|
+
files[f'migrations/002_{to_snake_case(entity_slug)}_metas.sql'] = generate_metas_migration(entity_slug, singular, pascal)
|
|
627
|
+
|
|
628
|
+
if args.dry_run:
|
|
629
|
+
print("DRY RUN - Files that would be created:\n")
|
|
630
|
+
for file_path in files.keys():
|
|
631
|
+
print(f" {base_path / file_path}")
|
|
632
|
+
print("\nRun without --dry-run to create files.")
|
|
633
|
+
return 0
|
|
634
|
+
|
|
635
|
+
# Create directories and files
|
|
636
|
+
for file_path, content in files.items():
|
|
637
|
+
full_path = base_path / file_path
|
|
638
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
639
|
+
|
|
640
|
+
if full_path.exists():
|
|
641
|
+
print(f" SKIP (exists): {full_path}")
|
|
642
|
+
else:
|
|
643
|
+
with open(full_path, 'w', encoding='utf-8') as f:
|
|
644
|
+
f.write(content)
|
|
645
|
+
print(f" CREATED: {full_path}")
|
|
646
|
+
|
|
647
|
+
print(f"\n{'=' * 60}")
|
|
648
|
+
print("NEXT STEPS:")
|
|
649
|
+
print("=" * 60)
|
|
650
|
+
print(f"1. Review and customize {entity_slug}.config.ts")
|
|
651
|
+
print(f"2. Add/modify fields in {entity_slug}.fields.ts")
|
|
652
|
+
print(f"3. Update translations in messages/en.json and es.json")
|
|
653
|
+
print(f"4. Run migration: pnpm db:migrate")
|
|
654
|
+
print(f"5. Regenerate registry: node core/scripts/build/registry.mjs")
|
|
655
|
+
print("=" * 60 + "\n")
|
|
656
|
+
|
|
657
|
+
return 0
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
if __name__ == '__main__':
|
|
661
|
+
sys.exit(main())
|