@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,444 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Scaffold Block - Generate Page Builder Block Structure
|
|
4
|
+
|
|
5
|
+
Creates the 5 required files for a new page builder block:
|
|
6
|
+
- config.ts
|
|
7
|
+
- schema.ts
|
|
8
|
+
- fields.ts
|
|
9
|
+
- component.tsx
|
|
10
|
+
- index.ts
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python scaffold-block.py [--theme THEME] [--slug SLUG] [--name NAME]
|
|
14
|
+
|
|
15
|
+
Interactive mode if no arguments provided.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
# Block categories
|
|
26
|
+
CATEGORIES = [
|
|
27
|
+
'hero', 'features', 'cta', 'content', 'testimonials',
|
|
28
|
+
'pricing', 'faq', 'stats', 'gallery', 'timeline',
|
|
29
|
+
'contact', 'newsletter', 'team', 'portfolio', 'custom'
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
# Common Lucide icons for blocks
|
|
33
|
+
COMMON_ICONS = [
|
|
34
|
+
'LayoutTemplate', 'Grid', 'List', 'Star', 'Quote',
|
|
35
|
+
'DollarSign', 'HelpCircle', 'BarChart', 'Image', 'Clock',
|
|
36
|
+
'Mail', 'Users', 'Briefcase', 'Sparkles', 'Box'
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def to_kebab_case(s: str) -> str:
|
|
41
|
+
"""Convert string to kebab-case."""
|
|
42
|
+
s = re.sub(r'([A-Z])', r'-\1', s)
|
|
43
|
+
s = re.sub(r'[\s_]+', '-', s)
|
|
44
|
+
return s.lower().strip('-')
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def to_pascal_case(s: str) -> str:
|
|
48
|
+
"""Convert string to PascalCase."""
|
|
49
|
+
return ''.join(word.capitalize() for word in re.split(r'[-_\s]+', s))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def to_camel_case(s: str) -> str:
|
|
53
|
+
"""Convert string to camelCase."""
|
|
54
|
+
pascal = to_pascal_case(s)
|
|
55
|
+
return pascal[0].lower() + pascal[1:] if pascal else ''
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_project_root() -> Path:
|
|
59
|
+
"""Get project root directory."""
|
|
60
|
+
# Look for package.json or .env
|
|
61
|
+
current = Path.cwd()
|
|
62
|
+
while current != current.parent:
|
|
63
|
+
if (current / 'package.json').exists():
|
|
64
|
+
return current
|
|
65
|
+
current = current.parent
|
|
66
|
+
return Path.cwd()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_active_theme(project_root: Path) -> str:
|
|
70
|
+
"""Get active theme from .env file."""
|
|
71
|
+
env_file = project_root / '.env'
|
|
72
|
+
if env_file.exists():
|
|
73
|
+
with open(env_file, 'r') as f:
|
|
74
|
+
for line in f:
|
|
75
|
+
if line.startswith('NEXT_PUBLIC_ACTIVE_THEME='):
|
|
76
|
+
return line.split('=', 1)[1].strip().strip('"\'')
|
|
77
|
+
return 'default'
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def prompt_input(prompt: str, default: str = None, choices: list = None) -> str:
|
|
81
|
+
"""Prompt user for input with optional default and choices."""
|
|
82
|
+
if choices:
|
|
83
|
+
print(f"\nAvailable options: {', '.join(choices)}")
|
|
84
|
+
|
|
85
|
+
if default:
|
|
86
|
+
result = input(f"{prompt} [{default}]: ").strip()
|
|
87
|
+
return result if result else default
|
|
88
|
+
else:
|
|
89
|
+
while True:
|
|
90
|
+
result = input(f"{prompt}: ").strip()
|
|
91
|
+
if result:
|
|
92
|
+
return result
|
|
93
|
+
print("This field is required.")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def validate_slug(slug: str, blocks_dir: Path) -> bool:
|
|
97
|
+
"""Validate block slug."""
|
|
98
|
+
if not re.match(r'^[a-z][a-z0-9-]*$', slug):
|
|
99
|
+
print(f"Error: Slug must be kebab-case (lowercase letters, numbers, hyphens)")
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
if (blocks_dir / slug).exists():
|
|
103
|
+
print(f"Error: Block '{slug}' already exists")
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def generate_config(slug: str, name: str, description: str, category: str, icon: str, scope: list) -> str:
|
|
110
|
+
"""Generate config.ts content."""
|
|
111
|
+
scope_str = str(scope).replace("'", '"')
|
|
112
|
+
return f'''import type {{ BlockConfig, BlockCategory }} from '@/core/types/blocks'
|
|
113
|
+
|
|
114
|
+
export const config: Omit<BlockConfig, 'schema' | 'fieldDefinitions' | 'Component' | 'examples'> = {{
|
|
115
|
+
slug: '{slug}',
|
|
116
|
+
name: '{name}',
|
|
117
|
+
description: '{description}',
|
|
118
|
+
category: '{category}' as BlockCategory,
|
|
119
|
+
icon: '{icon}',
|
|
120
|
+
thumbnail: '/theme/blocks/{slug}/thumbnail.png',
|
|
121
|
+
scope: {scope_str},
|
|
122
|
+
}}
|
|
123
|
+
'''
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def generate_schema(slug: str) -> str:
|
|
127
|
+
"""Generate schema.ts content."""
|
|
128
|
+
pascal = to_pascal_case(slug)
|
|
129
|
+
return f'''import {{ z }} from 'zod'
|
|
130
|
+
import {{ baseBlockSchema }} from '@/core/types/blocks'
|
|
131
|
+
|
|
132
|
+
// Extend baseBlockSchema with block-specific fields
|
|
133
|
+
// baseBlockSchema already includes: title, content, cta, backgroundColor, className, id
|
|
134
|
+
export const schema = baseBlockSchema.merge(z.object({{
|
|
135
|
+
// Add custom fields here
|
|
136
|
+
// Example: items: z.array(itemSchema).optional()
|
|
137
|
+
}}))
|
|
138
|
+
|
|
139
|
+
export type {pascal}Props = z.infer<typeof schema>
|
|
140
|
+
'''
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def generate_fields(slug: str) -> str:
|
|
144
|
+
"""Generate fields.ts content."""
|
|
145
|
+
return '''import type { FieldDefinition } from '@/core/types/blocks'
|
|
146
|
+
import {
|
|
147
|
+
baseContentFields,
|
|
148
|
+
baseDesignFields,
|
|
149
|
+
baseAdvancedFields,
|
|
150
|
+
} from '@/core/types/blocks'
|
|
151
|
+
|
|
152
|
+
// Custom content fields
|
|
153
|
+
const customContentFields: FieldDefinition[] = [
|
|
154
|
+
// Add custom content fields here
|
|
155
|
+
// Example:
|
|
156
|
+
// {
|
|
157
|
+
// name: 'subtitle',
|
|
158
|
+
// label: 'Subtitle',
|
|
159
|
+
// type: 'text',
|
|
160
|
+
// tab: 'content',
|
|
161
|
+
// },
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
// Custom design fields
|
|
165
|
+
const customDesignFields: FieldDefinition[] = [
|
|
166
|
+
// Add custom design fields here
|
|
167
|
+
// Example:
|
|
168
|
+
// {
|
|
169
|
+
// name: 'layout',
|
|
170
|
+
// label: 'Layout',
|
|
171
|
+
// type: 'select',
|
|
172
|
+
// tab: 'design',
|
|
173
|
+
// options: [
|
|
174
|
+
// { label: 'Left', value: 'left' },
|
|
175
|
+
// { label: 'Right', value: 'right' },
|
|
176
|
+
// ],
|
|
177
|
+
// },
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
// CRITICAL: Order matters - content -> design -> advanced
|
|
181
|
+
export const fieldDefinitions: FieldDefinition[] = [
|
|
182
|
+
...baseContentFields,
|
|
183
|
+
...customContentFields,
|
|
184
|
+
...baseDesignFields,
|
|
185
|
+
...customDesignFields,
|
|
186
|
+
...baseAdvancedFields, // MUST be last
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
// Compatibility alias
|
|
190
|
+
export const fields = fieldDefinitions
|
|
191
|
+
'''
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def generate_component(slug: str, name: str) -> str:
|
|
195
|
+
"""Generate component.tsx content."""
|
|
196
|
+
pascal = to_pascal_case(slug)
|
|
197
|
+
camel = to_camel_case(slug)
|
|
198
|
+
return f'''import {{ buildSectionClasses }} from '@/core/types/blocks'
|
|
199
|
+
import {{ sel }} from '../../lib/selectors'
|
|
200
|
+
import type {{ {pascal}Props }} from './schema'
|
|
201
|
+
|
|
202
|
+
export function {pascal}Block({{
|
|
203
|
+
// Base content props (from baseBlockSchema)
|
|
204
|
+
title,
|
|
205
|
+
content,
|
|
206
|
+
cta,
|
|
207
|
+
// Custom props
|
|
208
|
+
// Add destructured custom props here
|
|
209
|
+
// Base design props
|
|
210
|
+
backgroundColor,
|
|
211
|
+
// Base advanced props
|
|
212
|
+
className,
|
|
213
|
+
id,
|
|
214
|
+
}}: {pascal}Props) {{
|
|
215
|
+
// Use buildSectionClasses helper (NEVER hardcode colors)
|
|
216
|
+
const sectionClasses = buildSectionClasses('py-16 px-4 md:py-24', {{
|
|
217
|
+
backgroundColor,
|
|
218
|
+
className,
|
|
219
|
+
}})
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<section
|
|
223
|
+
id={{id}}
|
|
224
|
+
className={{sectionClasses}}
|
|
225
|
+
data-cy={{sel('blocks.{camel}.container')}}
|
|
226
|
+
>
|
|
227
|
+
<div className="container mx-auto">
|
|
228
|
+
{{title && (
|
|
229
|
+
<h2 className="text-3xl font-bold text-center mb-4">{{title}}</h2>
|
|
230
|
+
)}}
|
|
231
|
+
{{content && (
|
|
232
|
+
<div
|
|
233
|
+
className="text-center text-muted-foreground mb-8 max-w-2xl mx-auto"
|
|
234
|
+
dangerouslySetInnerHTML={{{{ __html: content }}}}
|
|
235
|
+
/>
|
|
236
|
+
)}}
|
|
237
|
+
|
|
238
|
+
{{/* Add custom block content here */}}
|
|
239
|
+
|
|
240
|
+
{{cta?.text && cta?.link && (
|
|
241
|
+
<div className="mt-8 text-center">
|
|
242
|
+
<a
|
|
243
|
+
href={{cta.link}}
|
|
244
|
+
target={{cta.target || '_self'}}
|
|
245
|
+
className="inline-flex items-center px-6 py-3 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
|
|
246
|
+
data-cy={{sel('blocks.{camel}.cta')}}
|
|
247
|
+
>
|
|
248
|
+
{{cta.text}}
|
|
249
|
+
</a>
|
|
250
|
+
</div>
|
|
251
|
+
)}}
|
|
252
|
+
</div>
|
|
253
|
+
</section>
|
|
254
|
+
)
|
|
255
|
+
}}
|
|
256
|
+
'''
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def generate_index(slug: str) -> str:
|
|
260
|
+
"""Generate index.ts content."""
|
|
261
|
+
pascal = to_pascal_case(slug)
|
|
262
|
+
return f'''export {{ config }} from './config'
|
|
263
|
+
export {{ schema }} from './schema'
|
|
264
|
+
export {{ fields, fieldDefinitions }} from './fields'
|
|
265
|
+
export {{ {pascal}Block as Component }} from './component'
|
|
266
|
+
|
|
267
|
+
export type {{ {pascal}Props }} from './schema'
|
|
268
|
+
'''
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def update_selectors(selectors_path: Path, slug: str) -> bool:
|
|
272
|
+
"""Add block selectors to BLOCK_SELECTORS."""
|
|
273
|
+
camel = to_camel_case(slug)
|
|
274
|
+
|
|
275
|
+
if not selectors_path.exists():
|
|
276
|
+
print(f"Warning: Selectors file not found at {selectors_path}")
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
with open(selectors_path, 'r') as f:
|
|
280
|
+
content = f.read()
|
|
281
|
+
|
|
282
|
+
# Check if already exists
|
|
283
|
+
if f"'{camel}':" in content or f'"{camel}":' in content:
|
|
284
|
+
print(f"Selectors for '{camel}' already exist")
|
|
285
|
+
return True
|
|
286
|
+
|
|
287
|
+
# Find BLOCK_SELECTORS and add entry
|
|
288
|
+
selector_entry = f''' {camel}: {{
|
|
289
|
+
container: 'block-{slug}',
|
|
290
|
+
}},'''
|
|
291
|
+
|
|
292
|
+
# Find the closing of BLOCK_SELECTORS object
|
|
293
|
+
match = re.search(r'(export const BLOCK_SELECTORS\s*=\s*\{[^}]*)(} as const)', content, re.DOTALL)
|
|
294
|
+
if match:
|
|
295
|
+
new_content = content[:match.end(1)] + '\n' + selector_entry + '\n' + match.group(2) + content[match.end():]
|
|
296
|
+
with open(selectors_path, 'w') as f:
|
|
297
|
+
f.write(new_content)
|
|
298
|
+
print(f"Added selectors for '{camel}' to BLOCK_SELECTORS")
|
|
299
|
+
return True
|
|
300
|
+
else:
|
|
301
|
+
print("Warning: Could not find BLOCK_SELECTORS in selectors file")
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def run_registry_build(project_root: Path) -> bool:
|
|
306
|
+
"""Run registry build script."""
|
|
307
|
+
print("\nRebuilding block registry...")
|
|
308
|
+
try:
|
|
309
|
+
result = subprocess.run(
|
|
310
|
+
['node', 'core/scripts/build/registry.mjs'],
|
|
311
|
+
cwd=project_root,
|
|
312
|
+
capture_output=True,
|
|
313
|
+
text=True
|
|
314
|
+
)
|
|
315
|
+
if result.returncode == 0:
|
|
316
|
+
print("Registry rebuilt successfully")
|
|
317
|
+
return True
|
|
318
|
+
else:
|
|
319
|
+
print(f"Registry build failed: {result.stderr}")
|
|
320
|
+
return False
|
|
321
|
+
except Exception as e:
|
|
322
|
+
print(f"Error running registry build: {e}")
|
|
323
|
+
return False
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def verify_block_in_registry(project_root: Path, slug: str) -> bool:
|
|
327
|
+
"""Verify block was added to registry."""
|
|
328
|
+
registry_path = project_root / 'core' / 'lib' / 'registries' / 'block-registry.ts'
|
|
329
|
+
if registry_path.exists():
|
|
330
|
+
with open(registry_path, 'r') as f:
|
|
331
|
+
content = f.read()
|
|
332
|
+
if f"'{slug}':" in content or f'"{slug}":' in content:
|
|
333
|
+
print(f"Block '{slug}' verified in BLOCK_REGISTRY")
|
|
334
|
+
return True
|
|
335
|
+
print(f"Warning: Block '{slug}' not found in BLOCK_REGISTRY")
|
|
336
|
+
return False
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def main():
|
|
340
|
+
parser = argparse.ArgumentParser(description='Scaffold a new page builder block')
|
|
341
|
+
parser.add_argument('--theme', help='Theme name (default: from .env)')
|
|
342
|
+
parser.add_argument('--slug', help='Block slug (kebab-case)')
|
|
343
|
+
parser.add_argument('--name', help='Block display name')
|
|
344
|
+
parser.add_argument('--description', help='Block description')
|
|
345
|
+
parser.add_argument('--category', help='Block category', choices=CATEGORIES)
|
|
346
|
+
parser.add_argument('--icon', help='Lucide icon name')
|
|
347
|
+
parser.add_argument('--scope', help='Scope: pages, posts, or both', default='pages')
|
|
348
|
+
args = parser.parse_args()
|
|
349
|
+
|
|
350
|
+
project_root = get_project_root()
|
|
351
|
+
print(f"Project root: {project_root}")
|
|
352
|
+
|
|
353
|
+
# Check if all required args provided (non-interactive mode)
|
|
354
|
+
non_interactive = all([args.theme or get_active_theme(project_root), args.slug, args.name])
|
|
355
|
+
|
|
356
|
+
# Get theme
|
|
357
|
+
theme = args.theme or get_active_theme(project_root)
|
|
358
|
+
if not non_interactive:
|
|
359
|
+
theme = prompt_input("Theme", default=theme)
|
|
360
|
+
|
|
361
|
+
blocks_dir = project_root / 'contents' / 'themes' / theme / 'blocks'
|
|
362
|
+
if not blocks_dir.exists():
|
|
363
|
+
print(f"Error: Theme blocks directory not found: {blocks_dir}")
|
|
364
|
+
sys.exit(1)
|
|
365
|
+
|
|
366
|
+
# Get block metadata
|
|
367
|
+
if non_interactive:
|
|
368
|
+
slug = to_kebab_case(args.slug)
|
|
369
|
+
if not validate_slug(slug, blocks_dir):
|
|
370
|
+
sys.exit(1)
|
|
371
|
+
name = args.name
|
|
372
|
+
description = args.description or f"{name} block"
|
|
373
|
+
category = args.category or 'content'
|
|
374
|
+
icon = args.icon or 'Box'
|
|
375
|
+
scope_input = args.scope or 'pages'
|
|
376
|
+
else:
|
|
377
|
+
while True:
|
|
378
|
+
slug = args.slug or prompt_input("Block slug (kebab-case)")
|
|
379
|
+
slug = to_kebab_case(slug)
|
|
380
|
+
if validate_slug(slug, blocks_dir):
|
|
381
|
+
break
|
|
382
|
+
args.slug = None # Reset to prompt again
|
|
383
|
+
|
|
384
|
+
name = args.name or prompt_input("Display name", default=to_pascal_case(slug).replace('', ' ').strip())
|
|
385
|
+
description = args.description or prompt_input("Description", default=f"{name} block")
|
|
386
|
+
|
|
387
|
+
print(f"\nCategories: {', '.join(CATEGORIES)}")
|
|
388
|
+
category = args.category or prompt_input("Category", default='content', choices=CATEGORIES)
|
|
389
|
+
|
|
390
|
+
print(f"\nCommon icons: {', '.join(COMMON_ICONS)}")
|
|
391
|
+
icon = args.icon or prompt_input("Lucide icon", default='Box')
|
|
392
|
+
|
|
393
|
+
scope_input = args.scope or prompt_input("Scope (pages, posts, both)", default='pages')
|
|
394
|
+
|
|
395
|
+
if category not in CATEGORIES:
|
|
396
|
+
category = 'custom'
|
|
397
|
+
if scope_input == 'both':
|
|
398
|
+
scope = ['pages', 'posts']
|
|
399
|
+
else:
|
|
400
|
+
scope = [scope_input]
|
|
401
|
+
|
|
402
|
+
# Create block directory
|
|
403
|
+
block_dir = blocks_dir / slug
|
|
404
|
+
block_dir.mkdir(parents=True, exist_ok=True)
|
|
405
|
+
print(f"\nCreating block at: {block_dir}")
|
|
406
|
+
|
|
407
|
+
# Generate files
|
|
408
|
+
files = {
|
|
409
|
+
'config.ts': generate_config(slug, name, description, category, icon, scope),
|
|
410
|
+
'schema.ts': generate_schema(slug),
|
|
411
|
+
'fields.ts': generate_fields(slug),
|
|
412
|
+
'component.tsx': generate_component(slug, name),
|
|
413
|
+
'index.ts': generate_index(slug),
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for filename, content in files.items():
|
|
417
|
+
filepath = block_dir / filename
|
|
418
|
+
with open(filepath, 'w') as f:
|
|
419
|
+
f.write(content)
|
|
420
|
+
print(f" Created: {filename}")
|
|
421
|
+
|
|
422
|
+
# Update selectors
|
|
423
|
+
selectors_path = project_root / 'contents' / 'themes' / theme / 'lib' / 'selectors.ts'
|
|
424
|
+
update_selectors(selectors_path, slug)
|
|
425
|
+
|
|
426
|
+
# Run registry build
|
|
427
|
+
run_registry_build(project_root)
|
|
428
|
+
|
|
429
|
+
# Verify
|
|
430
|
+
verify_block_in_registry(project_root, slug)
|
|
431
|
+
|
|
432
|
+
print(f"\n{'='*50}")
|
|
433
|
+
print(f"Block '{slug}' created successfully!")
|
|
434
|
+
print(f"\nNext steps:")
|
|
435
|
+
print(f" 1. Edit schema.ts to add custom fields")
|
|
436
|
+
print(f" 2. Edit fields.ts to add field definitions")
|
|
437
|
+
print(f" 3. Edit component.tsx to implement rendering")
|
|
438
|
+
print(f" 4. Run: node core/scripts/build/registry.mjs")
|
|
439
|
+
print(f" 5. Test block in page builder")
|
|
440
|
+
print(f"{'='*50}")
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
if __name__ == '__main__':
|
|
444
|
+
main()
|