@graph8/sdk 0.370.0 → 0.373.0
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/README.md +19 -0
- package/blocks/_foundation/README.md +14 -0
- package/blocks/_foundation/block.json +44 -0
- package/blocks/_foundation/components/confirm-spend-dialog.tsx +15 -8
- package/blocks/_foundation/components/empty-state.tsx +5 -2
- package/blocks/_foundation/components/list-select.tsx +112 -0
- package/blocks/_foundation/components/paging-notices.tsx +55 -0
- package/blocks/_foundation/components/screen/detail-panel.tsx +2 -1
- package/blocks/_foundation/components/screen/filter-controls.tsx +92 -0
- package/blocks/_foundation/components/screen/index.ts +1 -0
- package/blocks/_foundation/components/screen/split-view.tsx +1 -1
- package/blocks/_foundation/hooks/use-all-pages.ts +54 -0
- package/blocks/_foundation/hooks/use-load-more-list.ts +61 -0
- package/blocks/_foundation/hooks/use-url-filters.ts +93 -0
- package/blocks/_foundation/lib/paging.ts +57 -0
- package/blocks/_foundation/lib/use-debounced-value.ts +13 -0
- package/blocks/agents/README.md +81 -4
- package/blocks/agents/block.json +311 -2
- package/blocks/agents/components/agent-activity.tsx +112 -0
- package/blocks/agents/components/agent-approvals.tsx +135 -0
- package/blocks/agents/components/agent-builder.tsx +17 -3
- package/blocks/agents/components/agent-conversations.tsx +143 -0
- package/blocks/agents/components/agent-knowledge-panel.tsx +2 -2
- package/blocks/agents/components/agent-operators.tsx +164 -0
- package/blocks/agents/components/agent-run-detail.tsx +20 -3
- package/blocks/agents/components/agent-runs.tsx +3 -3
- package/blocks/agents/components/agent-skills-panel.tsx +7 -7
- package/blocks/agents/components/agent-test-history.tsx +121 -0
- package/blocks/agents/components/agents-screen.tsx +53 -11
- package/blocks/agents/components/assistant-sessions.tsx +150 -0
- package/blocks/agents/components/duplicate-agent-button.tsx +42 -0
- package/blocks/agents/components/from-template-button.tsx +65 -0
- package/blocks/agents/components/knowledge-browser.tsx +107 -0
- package/blocks/agents/components/knowledge-detail.tsx +156 -0
- package/blocks/agents/components/knowledge-form.tsx +69 -0
- package/blocks/agents/components/name-dialog.tsx +32 -0
- package/blocks/agents/components/receptionist-button.tsx +46 -0
- package/blocks/agents/components/save-as-template-button.tsx +32 -0
- package/blocks/agents/components/skill-editor.tsx +147 -0
- package/blocks/agents/components/skill-library.tsx +111 -0
- package/blocks/agents/lib/agent-types.ts +7 -1
- package/blocks/agents/lib/facts.tsx +13 -0
- package/blocks/agents/lib/knowledge-utils.ts +49 -0
- package/blocks/agents/lib/load-more.tsx +141 -0
- package/blocks/agents/lib/skill-utils.ts +91 -0
- package/blocks/agents/recipes/create-agent-and-attach-skill.ts +1 -1
- package/blocks/analytics/components/analytics-screen.tsx +16 -3
- package/blocks/analytics/components/kpi-tiles.tsx +1 -1
- package/blocks/analytics/components/report-builder.tsx +15 -2
- package/blocks/appointments/README.md +6 -0
- package/blocks/appointments/block.json +27 -0
- package/blocks/appointments/components/booking-page.tsx +2 -2
- package/blocks/appointments/components/meeting-contact-links.tsx +49 -0
- package/blocks/appointments/components/meeting-detail.tsx +2 -2
- package/blocks/appointments/components/meeting-list.tsx +29 -13
- package/blocks/appointments/components/record-meetings-tab.tsx +92 -0
- package/blocks/campaigns/components/campaign-assets.tsx +1 -1
- package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
- package/blocks/campaigns/components/campaign-metrics.tsx +1 -1
- package/blocks/campaigns/components/campaign-wizard.tsx +10 -5
- package/blocks/campaigns/components/icp-detail.tsx +11 -2
- package/blocks/campaigns/components/icp-editor.tsx +8 -3
- package/blocks/campaigns/components/persona-detail.tsx +11 -2
- package/blocks/campaigns/components/persona-editor.tsx +6 -1
- package/blocks/campaigns/lib/campaigns-utils.ts +4 -4
- package/blocks/coach/components/call-scorecard.tsx +7 -7
- package/blocks/company-intelligence/README.md +132 -0
- package/blocks/company-intelligence/block.json +318 -0
- package/blocks/company-intelligence/components/company-intelligence-screen.tsx +114 -0
- package/blocks/company-intelligence/components/company-intelligence-tab.tsx +88 -0
- package/blocks/company-intelligence/components/company-profile.tsx +68 -0
- package/blocks/company-intelligence/components/intelligence-sources.tsx +63 -0
- package/blocks/company-intelligence/components/report-detail.tsx +189 -0
- package/blocks/company-intelligence/components/research-reports.tsx +136 -0
- package/blocks/company-intelligence/components/research-run.tsx +161 -0
- package/blocks/company-intelligence/components/source-detail.tsx +117 -0
- package/blocks/company-intelligence/components/source-view.tsx +274 -0
- package/blocks/company-intelligence/components/wiki-document-detail.tsx +116 -0
- package/blocks/company-intelligence/components/wiki-documents.tsx +66 -0
- package/blocks/company-intelligence/hooks/use-intelligence.ts +173 -0
- package/blocks/company-intelligence/lib/intelligence-types.ts +230 -0
- package/blocks/company-intelligence/lib/safe-markdown.tsx +150 -0
- package/blocks/company-intelligence/recipes/read-competitors-and-news.ts +33 -0
- package/blocks/company-intelligence/recipes/research-your-website.ts +43 -0
- package/blocks/company-intelligence/recipes/write-the-research-reports.ts +54 -0
- package/blocks/copilot/components/copilot-memory.tsx +6 -2
- package/blocks/copilot/components/copilot-panel.tsx +1 -1
- package/blocks/copilot/components/copilot-team-chat.tsx +6 -1
- package/blocks/crm/README.md +6 -0
- package/blocks/crm/block.json +53 -4
- package/blocks/crm/components/companies-screen.tsx +23 -9
- package/blocks/crm/components/company-detail.tsx +2 -2
- package/blocks/crm/components/contact-detail.tsx +4 -4
- package/blocks/crm/components/contact-lookup-link.tsx +44 -0
- package/blocks/crm/components/contacts-screen.tsx +31 -13
- package/blocks/crm/components/dedupe-review.tsx +6 -1
- package/blocks/crm/components/import-wizard.tsx +3 -7
- package/blocks/crm/components/list-select.tsx +20 -0
- package/blocks/crm/components/record-engagement-tab.tsx +103 -0
- package/blocks/crm/components/record-form.tsx +1 -1
- package/blocks/crm/components/tasks-screen.tsx +35 -15
- package/blocks/crm/hooks/use-all-lists.ts +18 -0
- package/blocks/crm/hooks/use-page-query.ts +3 -1
- package/blocks/crm/lib/crm-types.ts +14 -0
- package/blocks/crm/lib/read-lists.ts +16 -0
- package/blocks/deals/README.md +24 -6
- package/blocks/deals/block.json +151 -2
- package/blocks/deals/components/close-deal-dialog.tsx +169 -0
- package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
- package/blocks/deals/components/deal-detail.tsx +2 -2
- package/blocks/deals/components/deal-filters.tsx +65 -0
- package/blocks/deals/components/deal-line-items.tsx +120 -0
- package/blocks/deals/components/deal-notes-tab.tsx +97 -0
- package/blocks/deals/components/deal-related.tsx +30 -9
- package/blocks/deals/components/deal-tasks-tab.tsx +88 -0
- package/blocks/deals/components/deals-screen.tsx +57 -22
- package/blocks/deals/components/forecast-view.tsx +4 -4
- package/blocks/deals/components/pipeline-board.tsx +8 -6
- package/blocks/deals/components/pipeline-editor.tsx +132 -0
- package/blocks/deals/components/record-deals-tab.tsx +62 -0
- package/blocks/deals/components/stage-editor.tsx +1 -1
- package/blocks/deals/hooks/use-pipeline-deals.ts +17 -4
- package/blocks/deals/lib/deal-extras.ts +156 -0
- package/blocks/dialer/README.md +14 -5
- package/blocks/dialer/block.json +17 -2
- package/blocks/dialer/components/call-contact-button.tsx +26 -9
- package/blocks/dialer/components/call-log-screen.tsx +4 -1
- package/blocks/dialer/components/call-log.tsx +16 -4
- package/blocks/dialer/components/call-queue.tsx +3 -3
- package/blocks/dialer/components/dialer-screen.tsx +69 -27
- package/blocks/dialer/components/disposition-panel.tsx +1 -1
- package/blocks/dialer/components/record-calls-tab.tsx +101 -0
- package/blocks/dialer/components/session-start-form.tsx +58 -11
- package/blocks/dialer/components/softphone.tsx +1 -1
- package/blocks/enrichment/README.md +16 -6
- package/blocks/enrichment/block.json +19 -7
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +76 -27
- package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
- package/blocks/enrichment/components/enrich-button.tsx +1 -1
- package/blocks/enrichment/components/enrichment-screen.tsx +70 -17
- package/blocks/enrichment/components/waterfall-config.tsx +27 -7
- package/blocks/enrichment/lib/enrichment-types.ts +39 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
- package/blocks/forms/README.md +8 -0
- package/blocks/forms/block.json +107 -0
- package/blocks/forms/components/form-activity.tsx +71 -0
- package/blocks/forms/components/form-backfill.tsx +108 -0
- package/blocks/forms/components/form-builder.tsx +1 -1
- package/blocks/forms/components/form-embed.tsx +1 -1
- package/blocks/forms/components/form-groups.tsx +151 -0
- package/blocks/forms/components/form-mapping-editor.tsx +6 -9
- package/blocks/forms/components/form-route-history.tsx +72 -0
- package/blocks/forms/components/form-saved-views.tsx +81 -0
- package/blocks/forms/components/forms-screen.tsx +51 -17
- package/blocks/forms/components/more-rows.tsx +30 -0
- package/blocks/forms/components/submissions-export.tsx +67 -0
- package/blocks/forms/components/submissions-table.tsx +15 -6
- package/blocks/forms/lib/forms-extra.ts +67 -0
- package/blocks/inbox/README.md +5 -0
- package/blocks/inbox/block.json +15 -0
- package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
- package/blocks/inbox/components/thread-badge.tsx +2 -2
- package/blocks/inbox/components/thread-view.tsx +1 -1
- package/blocks/integrations/README.md +54 -1
- package/blocks/integrations/block.json +189 -15
- package/blocks/integrations/components/ad-accounts.tsx +82 -0
- package/blocks/integrations/components/connection-health.tsx +59 -0
- package/blocks/integrations/components/crm-detail.tsx +20 -1
- package/blocks/integrations/components/crm-records.tsx +103 -0
- package/blocks/integrations/components/field-mappings.tsx +61 -0
- package/blocks/integrations/components/integration-list.tsx +4 -4
- package/blocks/integrations/components/integrations-screen.tsx +20 -3
- package/blocks/integrations/components/migration-panel.tsx +133 -0
- package/blocks/integrations/components/sugarcrm-connect.tsx +95 -0
- package/blocks/integrations/components/sync-status.tsx +6 -6
- package/blocks/integrations/components/sync-triggers.tsx +134 -0
- package/blocks/integrations/components/webhook-detail.tsx +26 -7
- package/blocks/integrations/components/webhook-manager.tsx +30 -17
- package/blocks/integrations/components/workspace-tools.tsx +127 -0
- package/blocks/integrations/lib/crm-utils.ts +39 -0
- package/blocks/integrations/lib/facts.tsx +13 -0
- package/blocks/integrations/lib/integrations-utils.ts +5 -0
- package/blocks/integrations/lib/load-more.tsx +141 -0
- package/blocks/intent/components/account-intent-score.tsx +5 -5
- package/blocks/intent/components/intent-feed.tsx +8 -8
- package/blocks/intent/components/intent-keyword-manager.tsx +8 -8
- package/blocks/intent/lib/intent-utils.ts +4 -4
- package/blocks/lifecycle/components/account-related.tsx +8 -1
- package/blocks/lifecycle/components/churn-list.tsx +5 -2
- package/blocks/lifecycle/components/customer-table.tsx +1 -1
- package/blocks/lists/README.md +7 -1
- package/blocks/lists/block.json +31 -3
- package/blocks/lists/components/add-company-people-to-list-button.tsx +73 -0
- package/blocks/lists/components/add-to-list-button.tsx +17 -7
- package/blocks/lists/components/list-picker.tsx +13 -3
- package/blocks/lists/components/list-table.tsx +1 -1
- package/blocks/lists/components/lists-screen.tsx +4 -2
- package/blocks/lists/components/record-lists-tab.tsx +99 -0
- package/blocks/lists/components/suppression-manager.tsx +27 -6
- package/blocks/lists/components/suppressions-screen.tsx +21 -12
- package/blocks/mailboxes/block.json +4 -0
- package/blocks/mailboxes/components/connect-mailbox.tsx +9 -8
- package/blocks/mailboxes/components/domain-detail.tsx +10 -4
- package/blocks/mailboxes/components/domain-purchase.tsx +14 -11
- package/blocks/mailboxes/components/mailbox-detail.tsx +3 -2
- package/blocks/mailboxes/components/mailbox-list.tsx +8 -3
- package/blocks/mailboxes/components/mailboxes-screen.tsx +8 -3
- package/blocks/mailboxes/components/warmup-health.tsx +20 -20
- package/blocks/mailboxes/lib/mailboxes-utils.ts +4 -4
- package/blocks/mailboxes/lib/use-provider-domains.ts +23 -0
- package/blocks/newsletter/README.md +12 -0
- package/blocks/newsletter/block.json +117 -0
- package/blocks/newsletter/components/issue-scheduler.tsx +2 -2
- package/blocks/newsletter/components/issue-versions.tsx +93 -0
- package/blocks/newsletter/components/more-rows.tsx +30 -0
- package/blocks/newsletter/components/newsletter-editor.tsx +1 -1
- package/blocks/newsletter/components/newsletter-list-syncs.tsx +121 -0
- package/blocks/newsletter/components/newsletter-screen.tsx +36 -13
- package/blocks/newsletter/components/newsletter-settings.tsx +211 -0
- package/blocks/newsletter/components/newsletter-stats.tsx +2 -2
- package/blocks/newsletter/components/newsletter-templates.tsx +133 -0
- package/blocks/newsletter/components/subscriber-table.tsx +5 -7
- package/blocks/newsletter/components/subscriber-timeline.tsx +65 -0
- package/blocks/newsletter/lib/newsletter-extra.ts +60 -0
- package/blocks/nurture/components/nurture-builder.tsx +10 -2
- package/blocks/nurture/components/nurture-enroll.tsx +6 -7
- package/blocks/nurture/components/nurture-form.tsx +5 -8
- package/blocks/nurture/components/nurture-screen.tsx +16 -5
- package/blocks/nurture/components/nurture-stats.tsx +5 -4
- package/blocks/objects/README.md +1 -1
- package/blocks/objects/block.json +13 -5
- package/blocks/objects/components/attribute-field-input.tsx +14 -1
- package/blocks/objects/components/object-record-detail.tsx +8 -1
- package/blocks/objects/components/object-schema-editor.tsx +1 -1
- package/blocks/objects/components/record-roles-panel.tsx +15 -5
- package/blocks/pages/README.md +14 -0
- package/blocks/pages/block.json +113 -1
- package/blocks/pages/components/more-rows.tsx +30 -0
- package/blocks/pages/components/page-ai-tools.tsx +196 -0
- package/blocks/pages/components/page-analytics.tsx +176 -0
- package/blocks/pages/components/page-clone.tsx +99 -0
- package/blocks/pages/components/page-domains.tsx +167 -0
- package/blocks/pages/components/page-editor.tsx +1 -1
- package/blocks/pages/components/page-generation-status.tsx +48 -0
- package/blocks/pages/components/page-tracking.tsx +137 -0
- package/blocks/pages/components/pages-screen.tsx +57 -16
- package/blocks/pages/components/seo-report.tsx +6 -1
- package/blocks/pages/lib/studio.ts +7 -0
- package/blocks/phone-numbers/README.md +74 -0
- package/blocks/phone-numbers/block.json +262 -0
- package/blocks/phone-numbers/components/a2p-registration.tsx +230 -0
- package/blocks/phone-numbers/components/assign-number.tsx +104 -0
- package/blocks/phone-numbers/components/number-search.tsx +239 -0
- package/blocks/phone-numbers/components/number-settings.tsx +134 -0
- package/blocks/phone-numbers/components/number-usage.tsx +97 -0
- package/blocks/phone-numbers/components/phone-number-detail.tsx +84 -0
- package/blocks/phone-numbers/components/phone-number-list.tsx +110 -0
- package/blocks/phone-numbers/components/phone-numbers-screen.tsx +201 -0
- package/blocks/phone-numbers/components/register-owned-number.tsx +98 -0
- package/blocks/phone-numbers/components/release-number.tsx +126 -0
- package/blocks/phone-numbers/hooks/use-phone-numbers.ts +58 -0
- package/blocks/phone-numbers/lib/phone-numbers-utils.ts +202 -0
- package/blocks/phone-numbers/recipes/01-search-and-buy-number.ts +49 -0
- package/blocks/phone-numbers/recipes/02-assign-number-to-rep.ts +25 -0
- package/blocks/phone-numbers/recipes/03-audit-numbers.ts +36 -0
- package/blocks/phone-numbers/recipes/04-register-text-messaging.ts +39 -0
- package/blocks/pickers/README.md +23 -0
- package/blocks/pickers/block.json +103 -0
- package/blocks/pickers/components/company-picker.tsx +90 -0
- package/blocks/pickers/components/contact-picker.tsx +131 -0
- package/blocks/pickers/components/member-picker.tsx +99 -0
- package/blocks/pickers/components/search-picker.tsx +98 -0
- package/blocks/pickers/hooks/use-current-member.ts +104 -0
- package/blocks/pickers/hooks/use-picker-pages.ts +73 -0
- package/blocks/pickers/lib/use-debounced.ts +13 -0
- package/blocks/quotes/README.md +13 -0
- package/blocks/quotes/block.json +48 -1
- package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
- package/blocks/quotes/components/quote-builder.tsx +1 -1
- package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
- package/blocks/quotes/components/quote-list.tsx +5 -3
- package/blocks/quotes/components/quote-related.tsx +3 -2
- package/blocks/quotes/components/quote-viewer.tsx +7 -2
- package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
- package/blocks/radar/components/competitor-battle-card.tsx +1 -1
- package/blocks/radar/components/radar-overview.tsx +4 -4
- package/blocks/radar/components/radar-screen.tsx +2 -2
- package/blocks/sequencer/README.md +6 -0
- package/blocks/sequencer/block.json +28 -1
- package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
- package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
- package/blocks/sequencer/components/sequence-builder.tsx +19 -20
- package/blocks/sequencer/components/sequence-detail.tsx +4 -2
- package/blocks/sequencer/components/sequence-enrollments.tsx +4 -2
- package/blocks/sequencer/components/sequence-list.tsx +1 -1
- package/blocks/sequencer/components/sequence-stats.tsx +8 -8
- package/blocks/sequencer/components/sequences-screen.tsx +3 -0
- package/blocks/sequencer/components/step-editor.tsx +11 -11
- package/blocks/sequencer/lib/sequencer-utils.ts +5 -5
- package/blocks/social-ads/README.md +8 -0
- package/blocks/social-ads/block.json +137 -4
- package/blocks/social-ads/components/audience-sync-detail.tsx +9 -3
- package/blocks/social-ads/components/audience-sync.tsx +6 -2
- package/blocks/social-ads/components/campaign-ads.tsx +166 -0
- package/blocks/social-ads/components/campaign-detail.tsx +6 -1
- package/blocks/social-ads/components/campaign-editor.tsx +134 -0
- package/blocks/social-ads/components/creative-concepts.tsx +74 -0
- package/blocks/social-ads/components/creative-detail.tsx +6 -1
- package/blocks/social-ads/components/creative-gallery.tsx +20 -2
- package/blocks/social-ads/components/creative-revisions.tsx +57 -0
- package/blocks/social-ads/components/creative-tools.tsx +176 -0
- package/blocks/social-ads/components/post-composer.tsx +4 -2
- package/blocks/social-ads/components/post-detail.tsx +3 -0
- package/blocks/social-ads/components/social-ideas.tsx +64 -0
- package/blocks/social-ads/components/social-performance.tsx +80 -0
- package/blocks/social-ads/components/social-screen.tsx +33 -6
- package/blocks/social-ads/lib/ads-extra.ts +67 -0
- package/blocks/surveys/README.md +91 -0
- package/blocks/surveys/block.json +299 -0
- package/blocks/surveys/components/account-nps.tsx +69 -0
- package/blocks/surveys/components/contact-survey-history.tsx +52 -0
- package/blocks/surveys/components/org-summary.tsx +82 -0
- package/blocks/surveys/components/sending-domains.tsx +113 -0
- package/blocks/surveys/components/survey-form.tsx +126 -0
- package/blocks/surveys/components/survey-responses.tsx +145 -0
- package/blocks/surveys/components/survey-results.tsx +72 -0
- package/blocks/surveys/components/survey-send-history.tsx +58 -0
- package/blocks/surveys/components/survey-send.tsx +162 -0
- package/blocks/surveys/components/survey-triggers.tsx +166 -0
- package/blocks/surveys/components/surveys-screen.tsx +234 -0
- package/blocks/surveys/lib/surveys-types.ts +228 -0
- package/blocks/surveys/recipes/create-an-nps-survey.ts +22 -0
- package/blocks/surveys/recipes/send-a-survey-and-follow-it.ts +27 -0
- package/blocks/surveys/recipes/survey-score-report.ts +27 -0
- package/blocks/visitors/components/snippet-install.tsx +18 -9
- package/blocks/visitors/components/traffic-chart.tsx +4 -4
- package/blocks/visitors/components/visitor-company-card.tsx +24 -14
- package/blocks/visitors/components/visitor-feed.tsx +5 -5
- package/blocks/webchat/components/chat-widget-config.tsx +1 -1
- package/blocks/webchat/components/conversation-list.tsx +1 -1
- package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
- package/blocks/workbench/components/run-progress.tsx +1 -1
- package/blocks/workbench/components/workbench-grid.tsx +1 -1
- package/blocks/workbench/components/workbench-screen.tsx +15 -5
- package/blocks/workbench/components/workbench-table-list.tsx +14 -2
- package/blocks/workflows/components/workflow-editor.tsx +1 -1
- package/dist/cli.js +536 -392
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +1 -1
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +1 -1
- package/dist/react.browser.js.map +1 -1
- package/dist/react.browser.mjs +1 -1
- package/dist/react.browser.mjs.map +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +1 -1
- package/dist/react.mjs.map +1 -1
- package/dist/types/blocks/brand.d.ts +11 -2
- package/dist/types/blocks/create.d.ts +7 -3
- package/dist/types/blocks/doctor.d.ts +1 -0
- package/dist/types/blocks/flows.d.ts +7 -1
- package/dist/types/blocks/orgBrand.d.ts +34 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/version.d.ts +2 -2
- package/docs/api/desk.d.ts +2 -2
- package/docs/api/desk.md +3 -3
- package/package.json +1 -1
- package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
- package/stacks/6sense/pages/contacts.tsx.tmpl +19 -0
- package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
- package/stacks/6sense.json +17 -4
- package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
- package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
- package/stacks/apollo/pages/contacts.tsx.tmpl +29 -95
- package/stacks/apollo/pages/deals.tsx.tmpl +17 -0
- package/stacks/apollo/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/apollo/pages/lists.tsx.tmpl +12 -0
- package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
- package/stacks/apollo/pages/sequences.tsx.tmpl +12 -0
- package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
- package/stacks/apollo.json +36 -8
- package/stacks/calendly/pages/companies.tsx.tmpl +19 -0
- package/stacks/calendly/pages/contacts.tsx.tmpl +21 -0
- package/stacks/calendly/pages/meetings.tsx.tmpl +13 -0
- package/stacks/calendly/pages/tasks.tsx.tmpl +19 -0
- package/stacks/calendly.json +8 -4
- package/stacks/clay/pages/companies.tsx.tmpl +17 -1
- package/stacks/clay/pages/contacts.tsx.tmpl +12 -0
- package/stacks/clay/pages/lists.tsx.tmpl +12 -0
- package/stacks/clay/pages/tasks.tsx.tmpl +19 -0
- package/stacks/clay.json +16 -3
- package/stacks/gong/pages/calls.tsx.tmpl +23 -0
- package/stacks/gong/pages/companies.tsx.tmpl +22 -0
- package/stacks/gong/pages/contacts.tsx.tmpl +32 -0
- package/stacks/gong/pages/lists.tsx.tmpl +12 -0
- package/stacks/gong/pages/meetings.tsx.tmpl +13 -0
- package/stacks/gong/pages/tasks.tsx.tmpl +19 -0
- package/stacks/gong.json +23 -6
- package/stacks/hootsuite/pages/companies.tsx.tmpl +22 -0
- package/stacks/hootsuite/pages/contacts.tsx.tmpl +28 -0
- package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
- package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
- package/stacks/hootsuite.json +9 -4
- package/stacks/hubspot/pages/companies.tsx.tmpl +14 -35
- package/stacks/hubspot/pages/contacts.tsx.tmpl +29 -95
- package/stacks/hubspot/pages/deals.tsx.tmpl +10 -1
- package/stacks/hubspot/pages/inbox.tsx.tmpl +12 -1
- package/stacks/hubspot/pages/lifecycle.tsx.tmpl +23 -0
- package/stacks/hubspot/pages/lists.tsx.tmpl +12 -0
- package/stacks/hubspot/pages/meetings.tsx.tmpl +13 -0
- package/stacks/hubspot/pages/sequences.tsx.tmpl +12 -0
- package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
- package/stacks/hubspot.json +21 -5
- package/stacks/intercom/pages/companies.tsx.tmpl +19 -0
- package/stacks/intercom/pages/contacts.tsx.tmpl +21 -0
- package/stacks/intercom/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/intercom/pages/tasks.tsx.tmpl +19 -0
- package/stacks/intercom.json +8 -4
- package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
- package/stacks/mailchimp/pages/contacts.tsx.tmpl +28 -0
- package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
- package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
- package/stacks/mailchimp.json +20 -5
- package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
- package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
- package/stacks/outreach/pages/contacts.tsx.tmpl +27 -66
- package/stacks/outreach/pages/inbox-contact.tsx.tmpl +13 -0
- package/stacks/outreach/pages/lists.tsx.tmpl +12 -0
- package/stacks/outreach/pages/meetings.tsx.tmpl +13 -0
- package/stacks/outreach/pages/sequences.tsx.tmpl +12 -0
- package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
- package/stacks/outreach.json +25 -7
- package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +15 -35
- package/stacks/pipedrive/pages/deals.tsx.tmpl +10 -1
- package/stacks/pipedrive/pages/inbox.tsx.tmpl +12 -1
- package/stacks/pipedrive/pages/meetings.tsx.tmpl +13 -0
- package/stacks/pipedrive/pages/tasks.tsx.tmpl +20 -0
- package/stacks/pipedrive.json +4 -2
- package/stacks/salesforce/pages/companies.tsx.tmpl +14 -35
- package/stacks/salesforce/pages/contacts.tsx.tmpl +20 -35
- package/stacks/salesforce/pages/deals.tsx.tmpl +10 -1
- package/stacks/salesforce/pages/lifecycle.tsx.tmpl +23 -0
- package/stacks/salesforce/pages/lists.tsx.tmpl +12 -0
- package/stacks/salesforce/pages/tasks.tsx.tmpl +20 -0
- package/stacks/salesforce.json +7 -3
- package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +12 -0
- package/stacks/zoominfo/pages/lists.tsx.tmpl +12 -0
- package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +16 -3
- package/styles/styles.css +1 -1
- package/templates/next-app/app/globals.css.tmpl +349 -64
- package/templates/next-app/components/app-shell.tsx.tmpl +6 -0
- package/templates/next-app/components/shell/quick-jump.tsx.tmpl +4 -0
package/dist/cli.js
CHANGED
|
@@ -36,8 +36,8 @@ __export(cli_exports, {
|
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(cli_exports);
|
|
38
38
|
var import_node_child_process = require("child_process");
|
|
39
|
-
var
|
|
40
|
-
var
|
|
39
|
+
var import_node_fs10 = __toESM(require("fs"));
|
|
40
|
+
var import_node_path11 = __toESM(require("path"));
|
|
41
41
|
|
|
42
42
|
// src/agentSetup.ts
|
|
43
43
|
var ALL_TARGETS = ["claude", "codex", "cursor"];
|
|
@@ -119,9 +119,9 @@ ${section}
|
|
|
119
119
|
}
|
|
120
120
|
function planAgentSetup(targets, read, catalog) {
|
|
121
121
|
const plans = [];
|
|
122
|
-
const add = (target,
|
|
123
|
-
const current = read(
|
|
124
|
-
plans.push({ target, path:
|
|
122
|
+
const add = (target, path12, next) => {
|
|
123
|
+
const current = read(path12);
|
|
124
|
+
plans.push({ target, path: path12, content: next, action: current === null ? "create" : current === next ? "unchanged" : "update" });
|
|
125
125
|
};
|
|
126
126
|
for (const target of targets) {
|
|
127
127
|
if (target === "claude") add(target, ".claude/skills/graph8-sdk/SKILL.md", claudeSkill(catalog));
|
|
@@ -158,7 +158,7 @@ var import_node_fs4 = __toESM(require("fs"));
|
|
|
158
158
|
var import_node_path5 = __toESM(require("path"));
|
|
159
159
|
|
|
160
160
|
// src/version.ts
|
|
161
|
-
var SDK_VERSION = "0.
|
|
161
|
+
var SDK_VERSION = "0.373.0";
|
|
162
162
|
var SDK_CLIENT_ID = `js-sdk/${SDK_VERSION}`;
|
|
163
163
|
|
|
164
164
|
// src/blocks/paths.ts
|
|
@@ -4618,6 +4618,10 @@ Brand an app made by \`create\` (run it in the app's root). Every option is opti
|
|
|
4618
4618
|
--logo <path> an svg, png, jpg, webp or gif (max 512 KB), copied to public/brand/ and shown in the sidebar
|
|
4619
4619
|
--app-url <url> white-label: where link-outs go (default https://app.graph8.com)
|
|
4620
4620
|
--link-label <text> white-label: what the platform is called in copy and links (default graph8)
|
|
4621
|
+
--from-org fill in what you did not pass from the organization's Brand Kit (needs G8_API_KEY in the
|
|
4622
|
+
environment or .env.local): name, primary colour, logo. Running brand with no options
|
|
4623
|
+
does the same when the kit has something. Values you pass always win; they replace what the app has now.
|
|
4624
|
+
--no-org-brand never read the Brand Kit
|
|
4621
4625
|
--dir <path> the app's root (default: current directory)
|
|
4622
4626
|
--dry-run print what would change, write nothing
|
|
4623
4627
|
Running it again with the same options changes nothing.`;
|
|
@@ -4650,17 +4654,30 @@ ${BRAND_USAGE}`);
|
|
|
4650
4654
|
if (takeBrandFlag(o, flag, next)) continue;
|
|
4651
4655
|
if (flag === "--dir") o.dir = next();
|
|
4652
4656
|
else if (arg === "--dry-run") o.dryRun = true;
|
|
4657
|
+
else if (arg === "--from-org") o.fromOrg = true;
|
|
4658
|
+
else if (arg === "--no-org-brand") o.noOrgBrand = true;
|
|
4653
4659
|
else throw new Error(`unknown option ${arg}
|
|
4654
4660
|
|
|
4655
4661
|
${BRAND_USAGE}`);
|
|
4656
4662
|
}
|
|
4657
|
-
if (
|
|
4663
|
+
if (o.fromOrg && o.noOrgBrand) throw new Error(`--from-org and --no-org-brand contradict each other
|
|
4664
|
+
|
|
4665
|
+
${BRAND_USAGE}`);
|
|
4666
|
+
if (!hasBrandInput(o) && !o.fromOrg) throw new Error(`brand needs at least one of --name, --brand-color, --dark-brand-color, --logo, --app-url, --link-label
|
|
4658
4667
|
|
|
4659
4668
|
${BRAND_USAGE}`);
|
|
4660
4669
|
return o;
|
|
4661
4670
|
}
|
|
4662
4671
|
var LOGO_TYPES = ["svg", "png", "jpg", "jpeg", "webp", "gif"];
|
|
4663
4672
|
var MAX_LOGO_BYTES = 512 * 1024;
|
|
4673
|
+
function mergeOrgBrand(explicit, org, skip = {}) {
|
|
4674
|
+
const e = explicit ?? {};
|
|
4675
|
+
const out = { ...e };
|
|
4676
|
+
if (e.name === void 0 && !skip.name && org.name) out.name = org.name;
|
|
4677
|
+
if (e.color === void 0 && e.darkColor === void 0 && org.color) out.color = org.color;
|
|
4678
|
+
if (e.logo === void 0 && org.logo) out.logo = org.logo;
|
|
4679
|
+
return out;
|
|
4680
|
+
}
|
|
4664
4681
|
function brandProblem(o, cwd) {
|
|
4665
4682
|
if (o.name !== void 0 && !o.name.trim()) return "--name cannot be empty";
|
|
4666
4683
|
if (o.linkLabel !== void 0 && !o.linkLabel.trim()) return "--link-label cannot be empty";
|
|
@@ -4731,7 +4748,11 @@ function same(abs, content) {
|
|
|
4731
4748
|
const cur = import_node_fs5.default.readFileSync(abs);
|
|
4732
4749
|
return Buffer.isBuffer(content) ? cur.equals(content) : cur.toString("utf8") === content;
|
|
4733
4750
|
}
|
|
4734
|
-
function runBrand(
|
|
4751
|
+
function runBrand(given, io, cwd, fallbackName, org) {
|
|
4752
|
+
const o = org && given.fromOrg ? mergeOrgBrand(given, org) : given;
|
|
4753
|
+
if (given.fromOrg && !hasBrandInput(o)) {
|
|
4754
|
+
return io.out("nothing to apply from the Brand Kit"), 0;
|
|
4755
|
+
}
|
|
4735
4756
|
const root = import_node_path6.default.resolve(cwd, o.dir);
|
|
4736
4757
|
if (!import_node_fs5.default.existsSync(import_node_path6.default.join(root, "package.json"))) {
|
|
4737
4758
|
io.err(`no package.json in ${root}. Run this from your app's root, or pass --dir.`);
|
|
@@ -4769,348 +4790,10 @@ ${BRAND_USAGE}`), 1;
|
|
|
4769
4790
|
return 0;
|
|
4770
4791
|
}
|
|
4771
4792
|
|
|
4772
|
-
// src/blocks/
|
|
4773
|
-
var
|
|
4774
|
-
var
|
|
4775
|
-
var
|
|
4776
|
-
|
|
4777
|
-
Scaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and
|
|
4778
|
-
install the stack's blocks into it. Run \`graph8-sdk list\` to see the stacks.
|
|
4779
|
-
--package-manager <npm|pnpm|yarn|bun> default: the one running the command, else npm
|
|
4780
|
-
--no-install write the files and blocks, do not install dependencies
|
|
4781
|
-
--dry-run print what would be written, write and install nothing
|
|
4782
|
-
--force allow a non-empty <dir> (template files are overwritten, and graph8.json
|
|
4783
|
-
is reset: blocks you added to that app earlier are no longer recorded)
|
|
4784
|
-
Then: \`npm run dev\` in <dir>, open http://localhost:3000 and paste a graph8 API key (local preview), and run
|
|
4785
|
-
\`npx @graph8/sdk doctor --stack <stack>\` (add --production before going live).
|
|
4786
|
-
|
|
4787
|
-
Or pick the features yourself, for any app (also: \`create custom --blocks ...\`):
|
|
4788
|
-
npx @graph8/sdk create --blocks <id,id,...> [--title "Acme CRM"] <dir> [options]
|
|
4789
|
-
Every requested block's dependencies come with it. The app gets one route and one sidebar entry per finished
|
|
4790
|
-
Screen of the requested blocks, plus the user-facing Screens of their dependencies (asking for deals also gives
|
|
4791
|
-
you crm's screens). \`/\` opens the analytics screen if you asked for one, else the first screen.
|
|
4792
|
-
--title <text> the app's name (default: graph8 app)
|
|
4793
|
-
--with-deps-screens also add the plain fallback page of dependency blocks that have no Screen
|
|
4794
|
-
--no-deps-screens add screens only for the blocks you named
|
|
4795
|
-
Run \`graph8-sdk list\` to see each block and which have Screens.
|
|
4796
|
-
|
|
4797
|
-
Brand it (any stack or --blocks; the same as running \`graph8-sdk brand\` in the new app afterwards):
|
|
4798
|
-
--name <text> the app's name (with --blocks, the same as --title)
|
|
4799
|
-
--brand-color <hex> primary colour, AA-checked; --dark-brand-color <hex> for dark mode
|
|
4800
|
-
--logo <path> svg/png/jpg/webp/gif, shown in the sidebar
|
|
4801
|
-
--app-url <url> white-label: where "Open in graph8" links go; --link-label <text>: what they call it
|
|
4802
|
-
A directory literally named \`custom\` would read as the alias: pass \`--blocks crm ./custom\`.`;
|
|
4803
|
-
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
4804
|
-
function parseCreateArgs(argv) {
|
|
4805
|
-
const o = { stack: "", dir: "", install: true, dryRun: false, force: false };
|
|
4806
|
-
const positional = [];
|
|
4807
|
-
for (let i = 0; i < argv.length; i++) {
|
|
4808
|
-
const arg = argv[i];
|
|
4809
|
-
const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
|
|
4810
|
-
const [flag, inline] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [arg, void 0];
|
|
4811
|
-
if (flag === "--package-manager") {
|
|
4812
|
-
const v = inline ?? argv[++i];
|
|
4813
|
-
if (!PACKAGE_MANAGERS.includes(v)) throw new Error(`--package-manager must be one of ${PACKAGE_MANAGERS.join(", ")}
|
|
4814
|
-
|
|
4815
|
-
${CREATE_USAGE}`);
|
|
4816
|
-
o.packageManager = v;
|
|
4817
|
-
} else if (flag === "--blocks") {
|
|
4818
|
-
const v = inline ?? argv[++i];
|
|
4819
|
-
if (v === void 0 || v.startsWith("--")) throw new Error(`--blocks needs a comma-separated list of block ids
|
|
4820
|
-
|
|
4821
|
-
${CREATE_USAGE}`);
|
|
4822
|
-
o.blocks = [...o.blocks ?? [], ...v.split(",").map((x) => x.trim()).filter(Boolean)];
|
|
4823
|
-
} else if (flag === "--title") {
|
|
4824
|
-
const v = inline ?? argv[++i];
|
|
4825
|
-
if (v === void 0 || v.startsWith("--")) throw new Error(`--title needs a value
|
|
4826
|
-
|
|
4827
|
-
${CREATE_USAGE}`);
|
|
4828
|
-
o.title = v;
|
|
4829
|
-
} else if (takeBrandFlag(o.brand ??= {}, flag, () => {
|
|
4830
|
-
const v = inline ?? argv[++i];
|
|
4831
|
-
if (v === void 0 || v.startsWith("--")) throw new Error(`${flag} needs a value
|
|
4832
|
-
|
|
4833
|
-
${CREATE_USAGE}`);
|
|
4834
|
-
return v;
|
|
4835
|
-
})) {
|
|
4836
|
-
} else if (arg === "--with-deps-screens") o.depsScreens = "with";
|
|
4837
|
-
else if (arg === "--no-deps-screens") o.depsScreens = "none";
|
|
4838
|
-
else if (arg === "--no-install") o.install = false;
|
|
4839
|
-
else if (arg === "--dry-run") o.dryRun = true;
|
|
4840
|
-
else if (arg === "--force") o.force = true;
|
|
4841
|
-
else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
|
|
4842
|
-
|
|
4843
|
-
${CREATE_USAGE}`);
|
|
4844
|
-
else positional.push(arg);
|
|
4845
|
-
}
|
|
4846
|
-
if (o.brand && !hasBrandInput(o.brand)) delete o.brand;
|
|
4847
|
-
if (o.brand?.name !== void 0 && o.title !== void 0 && o.brand.name !== o.title) throw new Error(`--name and --title name the same thing: pass one
|
|
4848
|
-
|
|
4849
|
-
${CREATE_USAGE}`);
|
|
4850
|
-
if (o.blocks || positional[0] === "custom") return parseCustom(o, positional);
|
|
4851
|
-
if (o.title || o.depsScreens) throw new Error(`--title, --with-deps-screens and --no-deps-screens go with --blocks
|
|
4852
|
-
|
|
4853
|
-
${CREATE_USAGE}`);
|
|
4854
|
-
if (positional.length !== 2) throw new Error(`create needs a stack and a directory
|
|
4855
|
-
|
|
4856
|
-
${CREATE_USAGE}`);
|
|
4857
|
-
[o.stack, o.dir] = positional;
|
|
4858
|
-
return o;
|
|
4859
|
-
}
|
|
4860
|
-
function parseCustom(o, positional) {
|
|
4861
|
-
const rest = positional[0] === "custom" ? positional.slice(1) : positional;
|
|
4862
|
-
if (!o.blocks?.length) throw new Error(`create custom needs --blocks <id,id,...> (see \`graph8-sdk list\`)
|
|
4863
|
-
|
|
4864
|
-
${CREATE_USAGE}`);
|
|
4865
|
-
if (rest.length !== 1) {
|
|
4866
|
-
throw new Error(`${rest.length ? "a stack name cannot be combined with --blocks; pass only the directory" : "create --blocks needs a directory"}
|
|
4867
|
-
|
|
4868
|
-
${CREATE_USAGE}`);
|
|
4869
|
-
}
|
|
4870
|
-
return { ...o, stack: "custom", dir: rest[0] };
|
|
4871
|
-
}
|
|
4872
|
-
function packageManagerFromUserAgent(ua) {
|
|
4873
|
-
const name = /^(npm|pnpm|yarn|bun)\//.exec(ua ?? "")?.[1];
|
|
4874
|
-
return name ?? "npm";
|
|
4875
|
-
}
|
|
4876
|
-
function appNameFor(dir) {
|
|
4877
|
-
const base = import_node_path7.default.basename(import_node_path7.default.resolve(dir)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|-+$/g, "");
|
|
4878
|
-
return base || "g8-app";
|
|
4879
|
-
}
|
|
4880
|
-
var quoted = (p) => /^[\w@%+=:,./-]+$/.test(p) ? p : JSON.stringify(p);
|
|
4881
|
-
function walk(dir, base = dir) {
|
|
4882
|
-
const out = [];
|
|
4883
|
-
for (const e of import_node_fs6.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4884
|
-
const full = import_node_path7.default.join(dir, e.name);
|
|
4885
|
-
if (e.isDirectory()) out.push(...walk(full, base));
|
|
4886
|
-
else out.push(posix(import_node_path7.default.relative(base, full)));
|
|
4887
|
-
}
|
|
4888
|
-
return out;
|
|
4889
|
-
}
|
|
4890
|
-
function outputName(rel) {
|
|
4891
|
-
const noExt = rel.replace(/\.tmpl$/, "");
|
|
4892
|
-
const dir = import_node_path7.default.posix.dirname(noExt);
|
|
4893
|
-
const base = import_node_path7.default.posix.basename(noExt).replace(/^dot-/, ".");
|
|
4894
|
-
return dir === "." ? base : `${dir}/${base}`;
|
|
4895
|
-
}
|
|
4896
|
-
function stackProblem(reg, stack) {
|
|
4897
|
-
const { missing } = stackStatus(reg, stack);
|
|
4898
|
-
if (missing.length) {
|
|
4899
|
-
const ready = reg.stacks.filter((s) => !stackStatus(reg, s).missing.length).map((s) => s.id);
|
|
4900
|
-
return [
|
|
4901
|
-
`stack "${stack.id}" is planned: these blocks are not in this version of @graph8/sdk yet: ${missing.join(", ")}.`,
|
|
4902
|
-
`Stacks you can create now: ${ready.join(", ") || "none"}. Or create one of those and add blocks as they ship (\`graph8-sdk add <block>\`).`
|
|
4903
|
-
].join("\n");
|
|
4904
|
-
}
|
|
4905
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4906
|
-
const homeIssue = homeProblem(stack);
|
|
4907
|
-
if (homeIssue) return homeIssue;
|
|
4908
|
-
for (const n of stack.nav) {
|
|
4909
|
-
const block = findBlock(reg, n.block);
|
|
4910
|
-
if (!stack.blocks.includes(n.block)) return `stack "${stack.id}" nav "${n.label}" uses block "${n.block}" which the stack does not include.`;
|
|
4911
|
-
if (!block?.components.some((c) => c.name === n.component)) return `stack "${stack.id}" nav "${n.label}" uses ${n.component}, which block "${n.block}" does not declare.`;
|
|
4912
|
-
if (/[\[\]]/.test(n.path)) return `stack "${stack.id}" nav "${n.label}" uses ${n.path}: dynamic segments cannot be a sidebar link.`;
|
|
4913
|
-
if (seen.has(n.path)) return `stack "${stack.id}" has two nav entries at ${n.path}.`;
|
|
4914
|
-
seen.add(n.path);
|
|
4915
|
-
if (n.page && (!reg.stacksRoot || !import_node_fs6.default.existsSync(import_node_path7.default.join(reg.stacksRoot, stack.id, n.page)))) {
|
|
4916
|
-
return `stack "${stack.id}" nav "${n.label}" uses page ${n.page}, which does not exist under stacks/${stack.id}/.`;
|
|
4917
|
-
}
|
|
4918
|
-
}
|
|
4919
|
-
return null;
|
|
4920
|
-
}
|
|
4921
|
-
function homeProblem(stack) {
|
|
4922
|
-
if (!stack.home) return null;
|
|
4923
|
-
if (!stack.nav.some((n) => n.path === stack.home)) return `stack "${stack.id}" has home ${stack.home}, which is not one of its nav paths.`;
|
|
4924
|
-
if (stack.home !== "/" && stack.nav.some((n) => n.path === "/")) return `stack "${stack.id}" has home ${stack.home} but also a nav entry at "/": drop one.`;
|
|
4925
|
-
return null;
|
|
4926
|
-
}
|
|
4927
|
-
function stackPageFile(reg, stack, n) {
|
|
4928
|
-
return n.page && reg.stacksRoot ? import_node_path7.default.join(reg.stacksRoot, stack.id, n.page) : null;
|
|
4929
|
-
}
|
|
4930
|
-
function homeRedirect(stack) {
|
|
4931
|
-
if (stack.home) return stack.home === "/" ? null : stack.home;
|
|
4932
|
-
return stack.nav.length && !stack.nav.some((n) => n.path === "/") ? stack.nav[0].path : null;
|
|
4933
|
-
}
|
|
4934
|
-
function renderPages(reg, stack, templates) {
|
|
4935
|
-
const pageTpl = import_node_fs6.default.readFileSync(import_node_path7.default.join(templates, "_pages", "route-page.tsx.tmpl"), "utf8");
|
|
4936
|
-
const files = stack.nav.map((n) => {
|
|
4937
|
-
const block = findBlock(reg, n.block);
|
|
4938
|
-
const comp = block.components.find((c) => c.name === n.component);
|
|
4939
|
-
const custom = stackPageFile(reg, stack, n);
|
|
4940
|
-
const vars2 = {
|
|
4941
|
-
stackId: stack.id,
|
|
4942
|
-
block: block.id,
|
|
4943
|
-
blockDir: `components/g8/${block.dir}`,
|
|
4944
|
-
component: n.component,
|
|
4945
|
-
importPath: componentImportPath(block, comp.file),
|
|
4946
|
-
propsJson: JSON.stringify(n.props ?? {}, null, 2)
|
|
4947
|
-
};
|
|
4948
|
-
return { rel: pageFileFor(n.path), content: render(custom ? import_node_fs6.default.readFileSync(custom, "utf8") : pageTpl, vars2) };
|
|
4949
|
-
});
|
|
4950
|
-
const to = homeRedirect(stack);
|
|
4951
|
-
if (to) {
|
|
4952
|
-
const redirect = import_node_fs6.default.readFileSync(import_node_path7.default.join(templates, "_pages", "redirect-page.tsx.tmpl"), "utf8");
|
|
4953
|
-
files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(to) }) });
|
|
4954
|
-
}
|
|
4955
|
-
return files;
|
|
4956
|
-
}
|
|
4957
|
-
function peerDependenciesFor(blocks, io, stack) {
|
|
4958
|
-
const peers = {};
|
|
4959
|
-
for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) peers[name] ??= range;
|
|
4960
|
-
const usesUi = blocks.some((b) => walk(b.root).some((f) => /\.(tsx?|jsx?)$/.test(f) && /["']@graph8\/sdk\/ui["'/]/.test(import_node_fs6.default.readFileSync(import_node_path7.default.join(b.root, f), "utf8"))));
|
|
4961
|
-
if (usesUi && io.sdkPackageJson && import_node_fs6.default.existsSync(io.sdkPackageJson)) {
|
|
4962
|
-
const sdk = JSON.parse(import_node_fs6.default.readFileSync(io.sdkPackageJson, "utf8"));
|
|
4963
|
-
for (const [name, range] of Object.entries(sdk.peerDependencies ?? {})) peers[name] ??= range;
|
|
4964
|
-
}
|
|
4965
|
-
if (navUsesExtras(stack.nav)) peers["lucide-react"] ??= lucideRange(io);
|
|
4966
|
-
return peers;
|
|
4967
|
-
}
|
|
4968
|
-
function lucideRange(io) {
|
|
4969
|
-
try {
|
|
4970
|
-
const sdk = JSON.parse(import_node_fs6.default.readFileSync(io.sdkPackageJson ?? "", "utf8"));
|
|
4971
|
-
return sdk.peerDependencies?.["lucide-react"] ?? ">=0.400.0";
|
|
4972
|
-
} catch {
|
|
4973
|
-
return ">=0.400.0";
|
|
4974
|
-
}
|
|
4975
|
-
}
|
|
4976
|
-
function mergedPackageJson(rendered, peers) {
|
|
4977
|
-
const pkg = JSON.parse(rendered);
|
|
4978
|
-
for (const [name, range] of Object.entries(peers)) pkg.dependencies[name] ??= range;
|
|
4979
|
-
pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
|
|
4980
|
-
return `${JSON.stringify(pkg, null, 2)}
|
|
4981
|
-
`;
|
|
4982
|
-
}
|
|
4983
|
-
function planCreate(reg, stack, appName, pm, io, app) {
|
|
4984
|
-
const templates = import_node_path7.default.join(io.templatesRoot, "next-app");
|
|
4985
|
-
const blocks = resolveWithDeps(reg, stack.blocks);
|
|
4986
|
-
const devCommand = pm === "npm" ? "npm run dev" : `${pm} dev`;
|
|
4987
|
-
const vars2 = {
|
|
4988
|
-
appName,
|
|
4989
|
-
sdkVersion: SDK_VERSION,
|
|
4990
|
-
stackId: stack.id,
|
|
4991
|
-
title: stack.title,
|
|
4992
|
-
titleJson: JSON.stringify(stack.title),
|
|
4993
|
-
...navParts(stack),
|
|
4994
|
-
devCommand,
|
|
4995
|
-
installCommand: `${pm} install`,
|
|
4996
|
-
doctorCommand: app ? "npx @graph8/sdk doctor" : `npx @graph8/sdk doctor --stack ${stack.id}`,
|
|
4997
|
-
screens: stack.nav.map((n) => `- ${n.label}: \`${n.path}\` (${n.block} block, \`${n.component}\`)`).join("\n") || "- (none yet: add a block with `npx @graph8/sdk add <block>`)"
|
|
4998
|
-
};
|
|
4999
|
-
const files = [];
|
|
5000
|
-
for (const rel of walk(templates).filter((f) => !f.startsWith("_pages/"))) {
|
|
5001
|
-
let content = render(import_node_fs6.default.readFileSync(import_node_path7.default.join(templates, rel), "utf8"), vars2);
|
|
5002
|
-
if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io, stack));
|
|
5003
|
-
files.push({ rel: outputName(rel), content });
|
|
5004
|
-
}
|
|
5005
|
-
files.push(...renderPages(reg, stack, templates));
|
|
5006
|
-
files.push({ rel: CONFIG_FILE, content: renderBrandConfig({ name: stack.title, ...DEFAULT_FIELDS }) });
|
|
5007
|
-
const ledger = { sdkVersion: SDK_VERSION, target: "components/g8", stack: stack.id, ...app ? { app } : {}, blocks: {} };
|
|
5008
|
-
files.push({ rel: "graph8.json", content: `${JSON.stringify(ledger, null, 2)}
|
|
5009
|
-
` });
|
|
5010
|
-
return files;
|
|
5011
|
-
}
|
|
5012
|
-
function dirProblem(abs, force) {
|
|
5013
|
-
if (!import_node_fs6.default.existsSync(abs)) return null;
|
|
5014
|
-
if (!import_node_fs6.default.statSync(abs).isDirectory()) return `${abs} exists and is not a directory.`;
|
|
5015
|
-
if (force || import_node_fs6.default.readdirSync(abs).length === 0) return null;
|
|
5016
|
-
return `${abs} is not empty. Pick a new directory, or pass --force to write into it (template files are overwritten).`;
|
|
5017
|
-
}
|
|
5018
|
-
function runCreate(opts, reg, io, cwd) {
|
|
5019
|
-
let app;
|
|
5020
|
-
let stack = opts.blocks ? void 0 : reg.stacks.find((s) => s.id === opts.stack);
|
|
5021
|
-
if (opts.blocks) {
|
|
5022
|
-
let composed;
|
|
5023
|
-
try {
|
|
5024
|
-
composed = composeApp(reg, opts.blocks, { title: opts.title, withDepsScreens: opts.depsScreens === "with", noDepsScreens: opts.depsScreens === "none" });
|
|
5025
|
-
} catch (e) {
|
|
5026
|
-
io.err(e.message);
|
|
5027
|
-
return 1;
|
|
5028
|
-
}
|
|
5029
|
-
stack = composed.stack;
|
|
5030
|
-
app = { title: stack.title, blocks: [...new Set(opts.blocks)], screens: composed.screens.map((x) => ({ block: x.block, component: x.component, path: x.path })) };
|
|
5031
|
-
composed.warnings.forEach((w) => io.err(`warning: ${w}`));
|
|
5032
|
-
} else if (!stack) {
|
|
5033
|
-
io.err(`unknown stack "${opts.stack}". Stacks: ${reg.stacks.map((s) => s.id).join(", ") || "none"} (\`graph8-sdk list\`).`);
|
|
5034
|
-
return 1;
|
|
5035
|
-
}
|
|
5036
|
-
if (!stack) return 1;
|
|
5037
|
-
const name = opts.brand?.name?.trim();
|
|
5038
|
-
if (name) {
|
|
5039
|
-
stack = { ...stack, title: name };
|
|
5040
|
-
if (app) app = { ...app, title: name };
|
|
5041
|
-
}
|
|
5042
|
-
const problem = stackProblem(reg, stack);
|
|
5043
|
-
if (problem) return io.err(problem), 1;
|
|
5044
|
-
const abs = import_node_path7.default.resolve(cwd, opts.dir);
|
|
5045
|
-
const dirIssue = dirProblem(abs, opts.force);
|
|
5046
|
-
if (dirIssue) return io.err(dirIssue), 1;
|
|
5047
|
-
const brandIssue = opts.brand ? brandProblem(opts.brand, cwd) : null;
|
|
5048
|
-
if (brandIssue) return io.err(brandIssue), 1;
|
|
5049
|
-
const pm = opts.packageManager ?? packageManagerFromUserAgent(io.userAgent);
|
|
5050
|
-
let files;
|
|
5051
|
-
try {
|
|
5052
|
-
files = planCreate(reg, stack, appNameFor(opts.dir), pm, io, app);
|
|
5053
|
-
} catch (e) {
|
|
5054
|
-
io.err(e.message);
|
|
5055
|
-
return 1;
|
|
5056
|
-
}
|
|
5057
|
-
const blocks = resolveWithDeps(reg, stack.blocks);
|
|
5058
|
-
io.out(`${opts.dryRun ? "Dry run: " : ""}creating "${stack.title}" (${app ? `custom: ${app.blocks.join(", ")}` : stack.id}) in ${opts.dir}`);
|
|
5059
|
-
for (const f of files) io.out(`${opts.dryRun ? "would write" : "write"} ${f.rel}`);
|
|
5060
|
-
if (opts.dryRun) {
|
|
5061
|
-
io.out(`would add blocks: ${blocks.map((b) => b.id).join(", ")}`);
|
|
5062
|
-
if (opts.brand) io.out(`would brand the app: ${Object.entries(opts.brand).map(([k, v]) => `${k} ${v}`).join(", ")}`);
|
|
5063
|
-
io.out(opts.install ? `would run: ${pm} install` : "would not install dependencies (--no-install)");
|
|
5064
|
-
return 0;
|
|
5065
|
-
}
|
|
5066
|
-
for (const f of files) {
|
|
5067
|
-
const dest = import_node_path7.default.join(abs, f.rel);
|
|
5068
|
-
import_node_fs6.default.mkdirSync(import_node_path7.default.dirname(dest), { recursive: true });
|
|
5069
|
-
import_node_fs6.default.writeFileSync(dest, f.content, "utf8");
|
|
5070
|
-
}
|
|
5071
|
-
return finish(opts, reg, stack, blocks, abs, pm, io, cwd);
|
|
5072
|
-
}
|
|
5073
|
-
function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
|
|
5074
|
-
for (const b of blocks) {
|
|
5075
|
-
const code = runAdd({ spec: b.id, dir: abs, target: "components/g8", dryRun: false, force: opts.force, install: false, wireScreens: false }, reg, io, cwd);
|
|
5076
|
-
if (code !== 0) {
|
|
5077
|
-
io.err(`adding block "${b.id}" failed; the app in ${opts.dir} is incomplete.`);
|
|
5078
|
-
return code;
|
|
5079
|
-
}
|
|
5080
|
-
}
|
|
5081
|
-
if (opts.brand) {
|
|
5082
|
-
io.out("branding the app (graph8-sdk brand)");
|
|
5083
|
-
const code = runBrand({ ...opts.brand, dir: abs }, io, cwd, stack.title);
|
|
5084
|
-
if (code !== 0) {
|
|
5085
|
-
io.err(`branding failed; the app in ${opts.dir} is complete but unbranded. Fix the option and run \`npx @graph8/sdk brand\` in it.`);
|
|
5086
|
-
return code;
|
|
5087
|
-
}
|
|
5088
|
-
}
|
|
5089
|
-
if (opts.install) {
|
|
5090
|
-
io.out(`installing dependencies: ${pm} install`);
|
|
5091
|
-
const code = io.spawn(pm, ["install"], abs);
|
|
5092
|
-
if (code !== 0) {
|
|
5093
|
-
io.err(`dependency install failed (exit ${code}). Run it yourself: cd ${quoted(opts.dir)} && ${pm} install`);
|
|
5094
|
-
return 1;
|
|
5095
|
-
}
|
|
5096
|
-
}
|
|
5097
|
-
const cd = opts.dir === "." ? "" : `cd ${quoted(opts.dir)} && `;
|
|
5098
|
-
io.out(
|
|
5099
|
-
[
|
|
5100
|
-
"",
|
|
5101
|
-
`Done. Next:`,
|
|
5102
|
-
` 1. ${cd}${opts.install ? "" : `${pm} install && `}${pm === "npm" ? "npm run dev" : `${pm} dev`}`,
|
|
5103
|
-
` 2. open http://localhost:3000 and paste a graph8 API key in the Connect graph8 screen (local preview: only`,
|
|
5104
|
-
` this computer can reach the app's /api/g8 route), or set G8_API_KEY in ${import_node_path7.default.posix.join(opts.dir, ".env.local")}`,
|
|
5105
|
-
` 3. ${cd}npx @graph8/sdk doctor${opts.blocks ? "" : ` --stack ${stack.id}`} (before going live: add --production)`
|
|
5106
|
-
].join("\n")
|
|
5107
|
-
);
|
|
5108
|
-
return 0;
|
|
5109
|
-
}
|
|
5110
|
-
|
|
5111
|
-
// src/blocks/doctor.ts
|
|
5112
|
-
var import_node_fs8 = __toESM(require("fs"));
|
|
5113
|
-
var import_node_path9 = __toESM(require("path"));
|
|
4793
|
+
// src/blocks/orgBrand.ts
|
|
4794
|
+
var import_node_fs8 = __toESM(require("fs"));
|
|
4795
|
+
var import_node_os = __toESM(require("os"));
|
|
4796
|
+
var import_node_path9 = __toESM(require("path"));
|
|
5114
4797
|
|
|
5115
4798
|
// src/generated/billable.ts
|
|
5116
4799
|
var BILLABLE_ROUTES = {
|
|
@@ -5935,7 +5618,7 @@ function warnIfTargetOrgIgnored(apiKey, headers) {
|
|
|
5935
5618
|
);
|
|
5936
5619
|
}
|
|
5937
5620
|
}
|
|
5938
|
-
async function request(baseUrl,
|
|
5621
|
+
async function request(baseUrl, path12, apiKey, opts = {}) {
|
|
5939
5622
|
const {
|
|
5940
5623
|
method = "GET",
|
|
5941
5624
|
body,
|
|
@@ -5948,7 +5631,7 @@ async function request(baseUrl, path11, apiKey, opts = {}) {
|
|
|
5948
5631
|
fetchImpl = fetch,
|
|
5949
5632
|
sleepImpl = defaultSleep
|
|
5950
5633
|
} = opts;
|
|
5951
|
-
const url = baseUrl.replace(/\/+$/, "") +
|
|
5634
|
+
const url = baseUrl.replace(/\/+$/, "") + path12 + buildQuery(query);
|
|
5952
5635
|
const finalHeaders = {
|
|
5953
5636
|
"Content-Type": "application/json",
|
|
5954
5637
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -6108,9 +5791,13 @@ function createApiClient(apiKey, apiUrl) {
|
|
|
6108
5791
|
return client;
|
|
6109
5792
|
}
|
|
6110
5793
|
|
|
6111
|
-
// src/blocks/
|
|
5794
|
+
// src/blocks/doctor.ts
|
|
6112
5795
|
var import_node_fs7 = __toESM(require("fs"));
|
|
6113
5796
|
var import_node_path8 = __toESM(require("path"));
|
|
5797
|
+
|
|
5798
|
+
// src/blocks/flows.ts
|
|
5799
|
+
var import_node_fs6 = __toESM(require("fs"));
|
|
5800
|
+
var import_node_path7 = __toESM(require("path"));
|
|
6114
5801
|
var IMPORT_RE2 = /(?:^|\n)\s*(?:import|export)\s[^'"`;]*?from\s*["']([^"']+)["']|(?:^|\n)\s*import\s*["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)/g;
|
|
6115
5802
|
var EXTENSIONS = ["", ".tsx", ".ts", "/index.tsx", "/index.ts"];
|
|
6116
5803
|
function specifiers(source) {
|
|
@@ -6119,18 +5806,18 @@ function specifiers(source) {
|
|
|
6119
5806
|
function resolveFile(base) {
|
|
6120
5807
|
for (const ext of EXTENSIONS) {
|
|
6121
5808
|
const candidate = base + ext;
|
|
6122
|
-
if (
|
|
5809
|
+
if (import_node_fs6.default.existsSync(candidate) && import_node_fs6.default.statSync(candidate).isFile()) return candidate;
|
|
6123
5810
|
}
|
|
6124
5811
|
return null;
|
|
6125
5812
|
}
|
|
6126
5813
|
function resolveImport(spec, from, reg) {
|
|
6127
|
-
if (spec.startsWith(".")) return resolveFile(
|
|
5814
|
+
if (spec.startsWith(".")) return resolveFile(import_node_path7.default.resolve(import_node_path7.default.dirname(from), spec));
|
|
6128
5815
|
const g8 = /^@\/g8\/([^/]+)\/(.+)$/.exec(spec);
|
|
6129
5816
|
if (!g8) return null;
|
|
6130
5817
|
const owner = findBlock(reg, g8[1]);
|
|
6131
|
-
if (owner) return resolveFile(
|
|
5818
|
+
if (owner) return resolveFile(import_node_path7.default.join(owner.root, "components", g8[2])) ?? resolveFile(import_node_path7.default.join(owner.root, g8[2]));
|
|
6132
5819
|
const foundation = findBlock(reg, "foundation");
|
|
6133
|
-
return foundation ? resolveFile(
|
|
5820
|
+
return foundation ? resolveFile(import_node_path7.default.join(foundation.root, g8[1], g8[2])) : null;
|
|
6134
5821
|
}
|
|
6135
5822
|
function resolveInstalled(spec, reg) {
|
|
6136
5823
|
const m = /^@\/components\/g8\/([^/]+)\/(.+)$/.exec(spec);
|
|
@@ -6138,7 +5825,7 @@ function resolveInstalled(spec, reg) {
|
|
|
6138
5825
|
if (!m || !block) return null;
|
|
6139
5826
|
const files = [...block.components, ...block.hooks, ...block.lib].map((x) => x.file);
|
|
6140
5827
|
const hit = files.find((f) => (installedRel(f) ?? f).replace(/\.(tsx|ts|jsx|js)$/, "") === m[2]);
|
|
6141
|
-
return hit ?
|
|
5828
|
+
return hit ? import_node_path7.default.join(block.root, hit) : null;
|
|
6142
5829
|
}
|
|
6143
5830
|
function seeds(reg, stack) {
|
|
6144
5831
|
const out = [];
|
|
@@ -6146,13 +5833,13 @@ function seeds(reg, stack) {
|
|
|
6146
5833
|
const comp = findBlock(reg, n.block)?.components.find((c) => c.name === n.component);
|
|
6147
5834
|
const block = findBlock(reg, n.block);
|
|
6148
5835
|
if (!block || !comp) continue;
|
|
6149
|
-
const pageFile = n.page && reg.stacksRoot ?
|
|
6150
|
-
if (!pageFile || !
|
|
6151
|
-
out.push(
|
|
5836
|
+
const pageFile = n.page && reg.stacksRoot ? import_node_path7.default.join(reg.stacksRoot, stack.id, n.page) : null;
|
|
5837
|
+
if (!pageFile || !import_node_fs6.default.existsSync(pageFile)) {
|
|
5838
|
+
out.push(import_node_path7.default.join(block.root, comp.file));
|
|
6152
5839
|
continue;
|
|
6153
5840
|
}
|
|
6154
|
-
const source =
|
|
6155
|
-
if (source.includes("{{importPath}}")) out.push(
|
|
5841
|
+
const source = import_node_fs6.default.readFileSync(pageFile, "utf8");
|
|
5842
|
+
if (source.includes("{{importPath}}")) out.push(import_node_path7.default.join(block.root, comp.file));
|
|
6156
5843
|
for (const spec of specifiers(source)) {
|
|
6157
5844
|
const hit = resolveInstalled(spec, reg);
|
|
6158
5845
|
if (hit) out.push(hit);
|
|
@@ -6168,17 +5855,18 @@ function reachedFiles(reg, stack) {
|
|
|
6168
5855
|
if (seen.has(file)) continue;
|
|
6169
5856
|
seen.add(file);
|
|
6170
5857
|
if (!/\.(tsx?|jsx?)$/.test(file)) continue;
|
|
6171
|
-
for (const spec of specifiers(
|
|
5858
|
+
for (const spec of specifiers(import_node_fs6.default.readFileSync(file, "utf8"))) {
|
|
6172
5859
|
const next = resolveImport(spec, file, reg);
|
|
6173
5860
|
if (next && !seen.has(next)) queue.push(next);
|
|
6174
5861
|
}
|
|
6175
5862
|
}
|
|
6176
5863
|
return seen;
|
|
6177
5864
|
}
|
|
5865
|
+
var LIBRARY_BLOCKS = /* @__PURE__ */ new Set(["foundation", "pickers"]);
|
|
6178
5866
|
function unreachedComponents(reg, stack) {
|
|
6179
5867
|
const reached = reachedFiles(reg, stack);
|
|
6180
|
-
const blocks = resolveWithDeps(reg, stack.blocks).filter((b) => b.id
|
|
6181
|
-
return blocks.flatMap((b) => b.components.filter((c) => !reached.has(
|
|
5868
|
+
const blocks = resolveWithDeps(reg, stack.blocks).filter((b) => !LIBRARY_BLOCKS.has(b.id) && stack.blocks.includes(b.id));
|
|
5869
|
+
return blocks.flatMap((b) => b.components.filter((c) => !reached.has(import_node_path7.default.join(b.root, c.file))).map((c) => ({ block: b.id, component: c.name })));
|
|
6182
5870
|
}
|
|
6183
5871
|
|
|
6184
5872
|
// src/blocks/doctor.ts
|
|
@@ -6236,9 +5924,9 @@ function parseEnvFile(text) {
|
|
|
6236
5924
|
function readSetting(name, root, env) {
|
|
6237
5925
|
if (env[name]) return env[name];
|
|
6238
5926
|
for (const f of [".env.local"]) {
|
|
6239
|
-
const file =
|
|
6240
|
-
if (
|
|
6241
|
-
const v = parseEnvFile(
|
|
5927
|
+
const file = import_node_path8.default.join(root, f);
|
|
5928
|
+
if (import_node_fs7.default.existsSync(file)) {
|
|
5929
|
+
const v = parseEnvFile(import_node_fs7.default.readFileSync(file, "utf8"))[name];
|
|
6242
5930
|
if (v) return v;
|
|
6243
5931
|
}
|
|
6244
5932
|
}
|
|
@@ -6259,7 +5947,7 @@ function selectBlocks(opts, reg, root) {
|
|
|
6259
5947
|
}
|
|
6260
5948
|
if (!ids.length) {
|
|
6261
5949
|
try {
|
|
6262
|
-
const ledger = JSON.parse(
|
|
5950
|
+
const ledger = JSON.parse(import_node_fs7.default.readFileSync(import_node_path8.default.join(root, "graph8.json"), "utf8"));
|
|
6263
5951
|
ids = Object.keys(ledger.blocks ?? {});
|
|
6264
5952
|
} catch {
|
|
6265
5953
|
ids = [];
|
|
@@ -6273,7 +5961,7 @@ function errorText(e, key) {
|
|
|
6273
5961
|
return `${status ? `HTTP ${status}: ` : ""}${msg}`.split(key).join("[redacted]");
|
|
6274
5962
|
}
|
|
6275
5963
|
async function runDoctor(opts, reg, io, cwd) {
|
|
6276
|
-
const root =
|
|
5964
|
+
const root = import_node_path8.default.resolve(cwd, opts.dir);
|
|
6277
5965
|
let blocks;
|
|
6278
5966
|
try {
|
|
6279
5967
|
blocks = selectBlocks(opts, reg, root);
|
|
@@ -6359,7 +6047,7 @@ function printFlows(opts, reg, out, line) {
|
|
|
6359
6047
|
function printAppScreens(root, reg, out) {
|
|
6360
6048
|
let app;
|
|
6361
6049
|
try {
|
|
6362
|
-
app = JSON.parse(
|
|
6050
|
+
app = JSON.parse(import_node_fs7.default.readFileSync(import_node_path8.default.join(root, "graph8.json"), "utf8")).app;
|
|
6363
6051
|
} catch {
|
|
6364
6052
|
return;
|
|
6365
6053
|
}
|
|
@@ -6382,14 +6070,14 @@ function printChecklist(blocks, out) {
|
|
|
6382
6070
|
}
|
|
6383
6071
|
function readLocalKey(root) {
|
|
6384
6072
|
try {
|
|
6385
|
-
return
|
|
6073
|
+
return import_node_fs7.default.readFileSync(import_node_path8.default.join(root, ".g8", "local-key"), "utf8").trim() || void 0;
|
|
6386
6074
|
} catch {
|
|
6387
6075
|
return void 0;
|
|
6388
6076
|
}
|
|
6389
6077
|
}
|
|
6390
6078
|
var ROUTE_FILES = ["app/api/g8/[...path]/route.ts", "src/app/api/g8/[...path]/route.ts"];
|
|
6391
6079
|
function findRoute(root) {
|
|
6392
|
-
return ROUTE_FILES.find((f) =>
|
|
6080
|
+
return ROUTE_FILES.find((f) => import_node_fs7.default.existsSync(import_node_path8.default.join(root, f))) ?? null;
|
|
6393
6081
|
}
|
|
6394
6082
|
function checkRoute(root, blocks, production, out, line) {
|
|
6395
6083
|
const rel = findRoute(root);
|
|
@@ -6397,7 +6085,7 @@ function checkRoute(root, blocks, production, out, line) {
|
|
|
6397
6085
|
if (production) line(false, `no proxy route found (${ROUTE_FILES[0]}): run doctor from the app's root or pass --dir`);
|
|
6398
6086
|
return;
|
|
6399
6087
|
}
|
|
6400
|
-
const facts = inspectRoute(
|
|
6088
|
+
const facts = inspectRoute(import_node_fs7.default.readFileSync(import_node_path8.default.join(root, rel), "utf8"));
|
|
6401
6089
|
out(`
|
|
6402
6090
|
Proxy route (${rel})${production ? ", production gate" : ""}:`);
|
|
6403
6091
|
const problem = (text) => production ? line(false, text) : out(`warn ${text}`);
|
|
@@ -6431,8 +6119,8 @@ function fixPrefixes(root, blocks, io) {
|
|
|
6431
6119
|
io.err("no blocks found: graph8.json lists none (or pass --blocks / --stack).");
|
|
6432
6120
|
return 1;
|
|
6433
6121
|
}
|
|
6434
|
-
const file =
|
|
6435
|
-
const source =
|
|
6122
|
+
const file = import_node_path8.default.join(root, rel);
|
|
6123
|
+
const source = import_node_fs7.default.readFileSync(file, "utf8");
|
|
6436
6124
|
const prefixes = derivePrefixes(blocks);
|
|
6437
6125
|
const next = applyPrefixes(source, prefixes) ?? upgradeLegacyRoute(source, prefixes);
|
|
6438
6126
|
if (next === null) {
|
|
@@ -6442,12 +6130,448 @@ function fixPrefixes(root, blocks, io) {
|
|
|
6442
6130
|
}
|
|
6443
6131
|
if (next === source) io.out(`keep ${rel} (allowedPrefixes already match the ${prefixes.length} areas of the installed blocks)`);
|
|
6444
6132
|
else {
|
|
6445
|
-
|
|
6133
|
+
import_node_fs7.default.writeFileSync(file, next, "utf8");
|
|
6446
6134
|
io.out(`update ${rel} (allowedPrefixes: ${prefixes.join(", ")})`);
|
|
6447
6135
|
}
|
|
6448
6136
|
return 0;
|
|
6449
6137
|
}
|
|
6450
6138
|
|
|
6139
|
+
// src/blocks/orgBrand.ts
|
|
6140
|
+
var TIMEOUT_MS = 8e3;
|
|
6141
|
+
var NONE = (info) => ({ values: {}, info, cleanup: () => {
|
|
6142
|
+
} });
|
|
6143
|
+
function colorOf(v) {
|
|
6144
|
+
const raw = typeof v === "string" ? v : v && typeof v === "object" ? v.value : null;
|
|
6145
|
+
return typeof raw === "string" ? normalizeHex(raw) : null;
|
|
6146
|
+
}
|
|
6147
|
+
function pickColor(tokens) {
|
|
6148
|
+
const t = tokens ?? {};
|
|
6149
|
+
for (const k of ["primary", "brand", "accent"]) {
|
|
6150
|
+
const c = colorOf(t.colors?.[k]);
|
|
6151
|
+
if (c) return c;
|
|
6152
|
+
}
|
|
6153
|
+
return colorOf(t.components?.button?.background);
|
|
6154
|
+
}
|
|
6155
|
+
function cleanName(v) {
|
|
6156
|
+
if (typeof v !== "string") return null;
|
|
6157
|
+
const s = v.replace(/[\u0000-\u001f\u007f<>`$\\]/g, "").replace(/\s+/g, " ").trim().slice(0, 60);
|
|
6158
|
+
return s || null;
|
|
6159
|
+
}
|
|
6160
|
+
var MIME = { "image/svg+xml": "svg", "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif" };
|
|
6161
|
+
function privateHost(host) {
|
|
6162
|
+
const h = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
6163
|
+
return h === "localhost" || h.endsWith(".localhost") || h.endsWith(".internal") || /^(127\.|10\.|0\.|169\.254\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(h) || h === "::1" || /^(fc|fd|fe80)/.test(h);
|
|
6164
|
+
}
|
|
6165
|
+
async function downloadLogo(url, f) {
|
|
6166
|
+
let bytes;
|
|
6167
|
+
let ext;
|
|
6168
|
+
if (url.startsWith("data:")) {
|
|
6169
|
+
const m = /^data:([^;,]+)(;base64)?,(.*)$/s.exec(url);
|
|
6170
|
+
if (!m) return null;
|
|
6171
|
+
ext = MIME[m[1].toLowerCase()];
|
|
6172
|
+
bytes = m[2] ? Buffer.from(m[3], "base64") : Buffer.from(decodeURIComponent(m[3]));
|
|
6173
|
+
} else {
|
|
6174
|
+
const u = new URL(url);
|
|
6175
|
+
if (u.protocol !== "https:" || privateHost(u.hostname)) return null;
|
|
6176
|
+
const res = await f(u, { redirect: "error", signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
6177
|
+
if (!res.ok) return null;
|
|
6178
|
+
const type = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
6179
|
+
ext = MIME[type] ?? (type && type !== "application/octet-stream" ? void 0 : LOGO_TYPES.find((e) => u.pathname.toLowerCase().endsWith(`.${e}`)));
|
|
6180
|
+
bytes = Buffer.from(await res.arrayBuffer());
|
|
6181
|
+
}
|
|
6182
|
+
if (!ext || !LOGO_TYPES.includes(ext) || !bytes.length || bytes.length > MAX_LOGO_BYTES) return null;
|
|
6183
|
+
const dir = import_node_fs8.default.mkdtempSync(import_node_path9.default.join(import_node_os.default.tmpdir(), "g8-org-logo-"));
|
|
6184
|
+
const file = import_node_path9.default.join(dir, `logo.${ext}`);
|
|
6185
|
+
import_node_fs8.default.writeFileSync(file, bytes);
|
|
6186
|
+
return { file, dir };
|
|
6187
|
+
}
|
|
6188
|
+
async function tryCall(call, op) {
|
|
6189
|
+
try {
|
|
6190
|
+
return { data: (await call(op))?.data };
|
|
6191
|
+
} catch (e) {
|
|
6192
|
+
return { status: e.status ?? 0 };
|
|
6193
|
+
}
|
|
6194
|
+
}
|
|
6195
|
+
async function pullOrgBrand(io, root) {
|
|
6196
|
+
try {
|
|
6197
|
+
const key = readSetting("G8_API_KEY", root, io.env);
|
|
6198
|
+
if (!key) return NONE("org brand: no G8_API_KEY found, using the default look (set one to pick up your Brand Kit, or pass --no-org-brand)");
|
|
6199
|
+
const api = createApiClient(key, readSetting("G8_API_URL", root, io.env));
|
|
6200
|
+
const f = io.fetch ?? fetch;
|
|
6201
|
+
const call = (op) => api.call(op, void 0, { fetchImpl: (u, i) => f(u, { ...i, signal: AbortSignal.timeout(TIMEOUT_MS) }), maxRetries: 0 });
|
|
6202
|
+
const [kit, me] = await Promise.all([tryCall(call, "get_brand_kit_overview_brand_kit_get"), tryCall(call, "describe_current_key_me_get")]);
|
|
6203
|
+
if (!kit.data) return NONE(`org brand: the Brand Kit could not be read (${kit.status ? `HTTP ${kit.status}` : "unreachable"}), using the default look`);
|
|
6204
|
+
const values = {};
|
|
6205
|
+
const color = pickColor(kit.data.design_tokens);
|
|
6206
|
+
if (color) values.color = color;
|
|
6207
|
+
const name = cleanName(me.data?.org_name);
|
|
6208
|
+
if (name) values.name = name;
|
|
6209
|
+
let dir = null;
|
|
6210
|
+
const logoUrl = typeof kit.data.logo_url === "string" ? kit.data.logo_url.trim() : "";
|
|
6211
|
+
if (logoUrl) {
|
|
6212
|
+
try {
|
|
6213
|
+
const dl = await downloadLogo(logoUrl, f);
|
|
6214
|
+
if (dl) values.logo = dl.file, dir = dl.dir;
|
|
6215
|
+
} catch {
|
|
6216
|
+
}
|
|
6217
|
+
}
|
|
6218
|
+
const got = [values.name && "name", values.color && "colour", values.logo && "logo"].filter(Boolean);
|
|
6219
|
+
const cleanup = () => dir && import_node_fs8.default.rmSync(dir, { recursive: true, force: true });
|
|
6220
|
+
if (!got.length) return NONE("org brand: the Brand Kit is empty, using the default look");
|
|
6221
|
+
return { values, info: `org brand: using your Brand Kit (${got.join(", ")}); explicit flags win, --no-org-brand skips this`, cleanup };
|
|
6222
|
+
} catch {
|
|
6223
|
+
return NONE("org brand: could not reach the Brand Kit, using the default look");
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
|
|
6227
|
+
// src/blocks/create.ts
|
|
6228
|
+
var import_node_fs9 = __toESM(require("fs"));
|
|
6229
|
+
var import_node_path10 = __toESM(require("path"));
|
|
6230
|
+
var CREATE_USAGE = `Usage: npx @graph8/sdk create <stack> <dir> [options]
|
|
6231
|
+
|
|
6232
|
+
Scaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and
|
|
6233
|
+
install the stack's blocks into it. Run \`graph8-sdk list\` to see the stacks.
|
|
6234
|
+
--package-manager <npm|pnpm|yarn|bun> default: the one running the command, else npm
|
|
6235
|
+
--no-install write the files and blocks, do not install dependencies
|
|
6236
|
+
--dry-run print what would be written, write and install nothing
|
|
6237
|
+
--force allow a non-empty <dir> (template files are overwritten, and graph8.json
|
|
6238
|
+
is reset: blocks you added to that app earlier are no longer recorded)
|
|
6239
|
+
Then: \`npm run dev\` in <dir>, open http://localhost:3000 and paste a graph8 API key (local preview), and run
|
|
6240
|
+
\`npx @graph8/sdk doctor --stack <stack>\` (add --production before going live).
|
|
6241
|
+
|
|
6242
|
+
Or pick the features yourself, for any app (also: \`create custom --blocks ...\`):
|
|
6243
|
+
npx @graph8/sdk create --blocks <id,id,...> [--title "Acme CRM"] <dir> [options]
|
|
6244
|
+
Every requested block's dependencies come with it. The app gets one route and one sidebar entry per finished
|
|
6245
|
+
Screen of the requested blocks, plus the user-facing Screens of their dependencies (asking for deals also gives
|
|
6246
|
+
you crm's screens). \`/\` opens the analytics screen if you asked for one, else the first screen.
|
|
6247
|
+
--title <text> the app's name (default: graph8 app)
|
|
6248
|
+
--with-deps-screens also add the plain fallback page of dependency blocks that have no Screen
|
|
6249
|
+
--no-deps-screens add screens only for the blocks you named
|
|
6250
|
+
Run \`graph8-sdk list\` to see each block and which have Screens.
|
|
6251
|
+
|
|
6252
|
+
Brand it (any stack or --blocks; the same as running \`graph8-sdk brand\` in the new app afterwards):
|
|
6253
|
+
--name <text> the app's name (with --blocks, the same as --title)
|
|
6254
|
+
--brand-color <hex> primary colour, AA-checked; --dark-brand-color <hex> for dark mode
|
|
6255
|
+
--logo <path> svg/png/jpg/webp/gif, shown in the sidebar
|
|
6256
|
+
--app-url <url> white-label: where "Open in graph8" links go; --link-label <text>: what they call it
|
|
6257
|
+
With a graph8 API key (G8_API_KEY in the environment or .env.local) the org's Brand Kit fills in what you did not
|
|
6258
|
+
pass: the app name, the primary colour and the logo (downloaded into public/brand/). No key, no kit or an error:
|
|
6259
|
+
the default look, one info line, never a failure. --no-org-brand skips it.
|
|
6260
|
+
A directory literally named \`custom\` would read as the alias: pass \`--blocks crm ./custom\`.`;
|
|
6261
|
+
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
6262
|
+
function parseCreateArgs(argv) {
|
|
6263
|
+
const o = { stack: "", dir: "", install: true, dryRun: false, force: false };
|
|
6264
|
+
const positional = [];
|
|
6265
|
+
for (let i = 0; i < argv.length; i++) {
|
|
6266
|
+
const arg = argv[i];
|
|
6267
|
+
const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
|
|
6268
|
+
const [flag, inline] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [arg, void 0];
|
|
6269
|
+
if (flag === "--package-manager") {
|
|
6270
|
+
const v = inline ?? argv[++i];
|
|
6271
|
+
if (!PACKAGE_MANAGERS.includes(v)) throw new Error(`--package-manager must be one of ${PACKAGE_MANAGERS.join(", ")}
|
|
6272
|
+
|
|
6273
|
+
${CREATE_USAGE}`);
|
|
6274
|
+
o.packageManager = v;
|
|
6275
|
+
} else if (flag === "--blocks") {
|
|
6276
|
+
const v = inline ?? argv[++i];
|
|
6277
|
+
if (v === void 0 || v.startsWith("--")) throw new Error(`--blocks needs a comma-separated list of block ids
|
|
6278
|
+
|
|
6279
|
+
${CREATE_USAGE}`);
|
|
6280
|
+
o.blocks = [...o.blocks ?? [], ...v.split(",").map((x) => x.trim()).filter(Boolean)];
|
|
6281
|
+
} else if (flag === "--title") {
|
|
6282
|
+
const v = inline ?? argv[++i];
|
|
6283
|
+
if (v === void 0 || v.startsWith("--")) throw new Error(`--title needs a value
|
|
6284
|
+
|
|
6285
|
+
${CREATE_USAGE}`);
|
|
6286
|
+
o.title = v;
|
|
6287
|
+
} else if (takeBrandFlag(o.brand ??= {}, flag, () => {
|
|
6288
|
+
const v = inline ?? argv[++i];
|
|
6289
|
+
if (v === void 0 || v.startsWith("--")) throw new Error(`${flag} needs a value
|
|
6290
|
+
|
|
6291
|
+
${CREATE_USAGE}`);
|
|
6292
|
+
return v;
|
|
6293
|
+
})) {
|
|
6294
|
+
} else if (arg === "--no-org-brand") o.orgBrand = false;
|
|
6295
|
+
else if (arg === "--with-deps-screens") o.depsScreens = "with";
|
|
6296
|
+
else if (arg === "--no-deps-screens") o.depsScreens = "none";
|
|
6297
|
+
else if (arg === "--no-install") o.install = false;
|
|
6298
|
+
else if (arg === "--dry-run") o.dryRun = true;
|
|
6299
|
+
else if (arg === "--force") o.force = true;
|
|
6300
|
+
else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
|
|
6301
|
+
|
|
6302
|
+
${CREATE_USAGE}`);
|
|
6303
|
+
else positional.push(arg);
|
|
6304
|
+
}
|
|
6305
|
+
if (o.brand && !hasBrandInput(o.brand)) delete o.brand;
|
|
6306
|
+
if (o.brand?.name !== void 0 && o.title !== void 0 && o.brand.name !== o.title) throw new Error(`--name and --title name the same thing: pass one
|
|
6307
|
+
|
|
6308
|
+
${CREATE_USAGE}`);
|
|
6309
|
+
if (o.blocks || positional[0] === "custom") return parseCustom(o, positional);
|
|
6310
|
+
if (o.title || o.depsScreens) throw new Error(`--title, --with-deps-screens and --no-deps-screens go with --blocks
|
|
6311
|
+
|
|
6312
|
+
${CREATE_USAGE}`);
|
|
6313
|
+
if (positional.length !== 2) throw new Error(`create needs a stack and a directory
|
|
6314
|
+
|
|
6315
|
+
${CREATE_USAGE}`);
|
|
6316
|
+
[o.stack, o.dir] = positional;
|
|
6317
|
+
return o;
|
|
6318
|
+
}
|
|
6319
|
+
function parseCustom(o, positional) {
|
|
6320
|
+
const rest = positional[0] === "custom" ? positional.slice(1) : positional;
|
|
6321
|
+
if (!o.blocks?.length) throw new Error(`create custom needs --blocks <id,id,...> (see \`graph8-sdk list\`)
|
|
6322
|
+
|
|
6323
|
+
${CREATE_USAGE}`);
|
|
6324
|
+
if (rest.length !== 1) {
|
|
6325
|
+
throw new Error(`${rest.length ? "a stack name cannot be combined with --blocks; pass only the directory" : "create --blocks needs a directory"}
|
|
6326
|
+
|
|
6327
|
+
${CREATE_USAGE}`);
|
|
6328
|
+
}
|
|
6329
|
+
return { ...o, stack: "custom", dir: rest[0] };
|
|
6330
|
+
}
|
|
6331
|
+
function packageManagerFromUserAgent(ua) {
|
|
6332
|
+
const name = /^(npm|pnpm|yarn|bun)\//.exec(ua ?? "")?.[1];
|
|
6333
|
+
return name ?? "npm";
|
|
6334
|
+
}
|
|
6335
|
+
function appNameFor(dir) {
|
|
6336
|
+
const base = import_node_path10.default.basename(import_node_path10.default.resolve(dir)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|-+$/g, "");
|
|
6337
|
+
return base || "g8-app";
|
|
6338
|
+
}
|
|
6339
|
+
var quoted = (p) => /^[\w@%+=:,./-]+$/.test(p) ? p : JSON.stringify(p);
|
|
6340
|
+
function walk(dir, base = dir) {
|
|
6341
|
+
const out = [];
|
|
6342
|
+
for (const e of import_node_fs9.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
6343
|
+
const full = import_node_path10.default.join(dir, e.name);
|
|
6344
|
+
if (e.isDirectory()) out.push(...walk(full, base));
|
|
6345
|
+
else out.push(posix(import_node_path10.default.relative(base, full)));
|
|
6346
|
+
}
|
|
6347
|
+
return out;
|
|
6348
|
+
}
|
|
6349
|
+
function outputName(rel) {
|
|
6350
|
+
const noExt = rel.replace(/\.tmpl$/, "");
|
|
6351
|
+
const dir = import_node_path10.default.posix.dirname(noExt);
|
|
6352
|
+
const base = import_node_path10.default.posix.basename(noExt).replace(/^dot-/, ".");
|
|
6353
|
+
return dir === "." ? base : `${dir}/${base}`;
|
|
6354
|
+
}
|
|
6355
|
+
function stackProblem(reg, stack) {
|
|
6356
|
+
const { missing } = stackStatus(reg, stack);
|
|
6357
|
+
if (missing.length) {
|
|
6358
|
+
const ready = reg.stacks.filter((s) => !stackStatus(reg, s).missing.length).map((s) => s.id);
|
|
6359
|
+
return [
|
|
6360
|
+
`stack "${stack.id}" is planned: these blocks are not in this version of @graph8/sdk yet: ${missing.join(", ")}.`,
|
|
6361
|
+
`Stacks you can create now: ${ready.join(", ") || "none"}. Or create one of those and add blocks as they ship (\`graph8-sdk add <block>\`).`
|
|
6362
|
+
].join("\n");
|
|
6363
|
+
}
|
|
6364
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6365
|
+
const homeIssue = homeProblem(stack);
|
|
6366
|
+
if (homeIssue) return homeIssue;
|
|
6367
|
+
for (const n of stack.nav) {
|
|
6368
|
+
const block = findBlock(reg, n.block);
|
|
6369
|
+
if (!stack.blocks.includes(n.block)) return `stack "${stack.id}" nav "${n.label}" uses block "${n.block}" which the stack does not include.`;
|
|
6370
|
+
if (!block?.components.some((c) => c.name === n.component)) return `stack "${stack.id}" nav "${n.label}" uses ${n.component}, which block "${n.block}" does not declare.`;
|
|
6371
|
+
if (/[\[\]]/.test(n.path)) return `stack "${stack.id}" nav "${n.label}" uses ${n.path}: dynamic segments cannot be a sidebar link.`;
|
|
6372
|
+
if (seen.has(n.path)) return `stack "${stack.id}" has two nav entries at ${n.path}.`;
|
|
6373
|
+
seen.add(n.path);
|
|
6374
|
+
if (n.page && (!reg.stacksRoot || !import_node_fs9.default.existsSync(import_node_path10.default.join(reg.stacksRoot, stack.id, n.page)))) {
|
|
6375
|
+
return `stack "${stack.id}" nav "${n.label}" uses page ${n.page}, which does not exist under stacks/${stack.id}/.`;
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
return null;
|
|
6379
|
+
}
|
|
6380
|
+
function homeProblem(stack) {
|
|
6381
|
+
if (!stack.home) return null;
|
|
6382
|
+
if (!stack.nav.some((n) => n.path === stack.home)) return `stack "${stack.id}" has home ${stack.home}, which is not one of its nav paths.`;
|
|
6383
|
+
if (stack.home !== "/" && stack.nav.some((n) => n.path === "/")) return `stack "${stack.id}" has home ${stack.home} but also a nav entry at "/": drop one.`;
|
|
6384
|
+
return null;
|
|
6385
|
+
}
|
|
6386
|
+
function stackPageFile(reg, stack, n) {
|
|
6387
|
+
return n.page && reg.stacksRoot ? import_node_path10.default.join(reg.stacksRoot, stack.id, n.page) : null;
|
|
6388
|
+
}
|
|
6389
|
+
function homeRedirect(stack) {
|
|
6390
|
+
if (stack.home) return stack.home === "/" ? null : stack.home;
|
|
6391
|
+
return stack.nav.length && !stack.nav.some((n) => n.path === "/") ? stack.nav[0].path : null;
|
|
6392
|
+
}
|
|
6393
|
+
function renderPages(reg, stack, templates) {
|
|
6394
|
+
const pageTpl = import_node_fs9.default.readFileSync(import_node_path10.default.join(templates, "_pages", "route-page.tsx.tmpl"), "utf8");
|
|
6395
|
+
const files = stack.nav.map((n) => {
|
|
6396
|
+
const block = findBlock(reg, n.block);
|
|
6397
|
+
const comp = block.components.find((c) => c.name === n.component);
|
|
6398
|
+
const custom = stackPageFile(reg, stack, n);
|
|
6399
|
+
const vars2 = {
|
|
6400
|
+
stackId: stack.id,
|
|
6401
|
+
block: block.id,
|
|
6402
|
+
blockDir: `components/g8/${block.dir}`,
|
|
6403
|
+
component: n.component,
|
|
6404
|
+
importPath: componentImportPath(block, comp.file),
|
|
6405
|
+
propsJson: JSON.stringify(n.props ?? {}, null, 2)
|
|
6406
|
+
};
|
|
6407
|
+
return { rel: pageFileFor(n.path), content: render(custom ? import_node_fs9.default.readFileSync(custom, "utf8") : pageTpl, vars2) };
|
|
6408
|
+
});
|
|
6409
|
+
const to = homeRedirect(stack);
|
|
6410
|
+
if (to) {
|
|
6411
|
+
const redirect = import_node_fs9.default.readFileSync(import_node_path10.default.join(templates, "_pages", "redirect-page.tsx.tmpl"), "utf8");
|
|
6412
|
+
files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(to) }) });
|
|
6413
|
+
}
|
|
6414
|
+
return files;
|
|
6415
|
+
}
|
|
6416
|
+
function peerDependenciesFor(blocks, io, stack) {
|
|
6417
|
+
const peers = {};
|
|
6418
|
+
for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) peers[name] ??= range;
|
|
6419
|
+
const usesUi = blocks.some((b) => walk(b.root).some((f) => /\.(tsx?|jsx?)$/.test(f) && /["']@graph8\/sdk\/ui["'/]/.test(import_node_fs9.default.readFileSync(import_node_path10.default.join(b.root, f), "utf8"))));
|
|
6420
|
+
if (usesUi && io.sdkPackageJson && import_node_fs9.default.existsSync(io.sdkPackageJson)) {
|
|
6421
|
+
const sdk = JSON.parse(import_node_fs9.default.readFileSync(io.sdkPackageJson, "utf8"));
|
|
6422
|
+
for (const [name, range] of Object.entries(sdk.peerDependencies ?? {})) peers[name] ??= range;
|
|
6423
|
+
}
|
|
6424
|
+
if (navUsesExtras(stack.nav)) peers["lucide-react"] ??= lucideRange(io);
|
|
6425
|
+
return peers;
|
|
6426
|
+
}
|
|
6427
|
+
function lucideRange(io) {
|
|
6428
|
+
try {
|
|
6429
|
+
const sdk = JSON.parse(import_node_fs9.default.readFileSync(io.sdkPackageJson ?? "", "utf8"));
|
|
6430
|
+
return sdk.peerDependencies?.["lucide-react"] ?? ">=0.400.0";
|
|
6431
|
+
} catch {
|
|
6432
|
+
return ">=0.400.0";
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
function mergedPackageJson(rendered, peers) {
|
|
6436
|
+
const pkg = JSON.parse(rendered);
|
|
6437
|
+
for (const [name, range] of Object.entries(peers)) pkg.dependencies[name] ??= range;
|
|
6438
|
+
pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
|
|
6439
|
+
return `${JSON.stringify(pkg, null, 2)}
|
|
6440
|
+
`;
|
|
6441
|
+
}
|
|
6442
|
+
function planCreate(reg, stack, appName, pm, io, app) {
|
|
6443
|
+
const templates = import_node_path10.default.join(io.templatesRoot, "next-app");
|
|
6444
|
+
const blocks = resolveWithDeps(reg, stack.blocks);
|
|
6445
|
+
const devCommand = pm === "npm" ? "npm run dev" : `${pm} dev`;
|
|
6446
|
+
const vars2 = {
|
|
6447
|
+
appName,
|
|
6448
|
+
sdkVersion: SDK_VERSION,
|
|
6449
|
+
stackId: stack.id,
|
|
6450
|
+
title: stack.title,
|
|
6451
|
+
titleJson: JSON.stringify(stack.title),
|
|
6452
|
+
...navParts(stack),
|
|
6453
|
+
devCommand,
|
|
6454
|
+
installCommand: `${pm} install`,
|
|
6455
|
+
doctorCommand: app ? "npx @graph8/sdk doctor" : `npx @graph8/sdk doctor --stack ${stack.id}`,
|
|
6456
|
+
screens: stack.nav.map((n) => `- ${n.label}: \`${n.path}\` (${n.block} block, \`${n.component}\`)`).join("\n") || "- (none yet: add a block with `npx @graph8/sdk add <block>`)"
|
|
6457
|
+
};
|
|
6458
|
+
const files = [];
|
|
6459
|
+
for (const rel of walk(templates).filter((f) => !f.startsWith("_pages/"))) {
|
|
6460
|
+
let content = render(import_node_fs9.default.readFileSync(import_node_path10.default.join(templates, rel), "utf8"), vars2);
|
|
6461
|
+
if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io, stack));
|
|
6462
|
+
files.push({ rel: outputName(rel), content });
|
|
6463
|
+
}
|
|
6464
|
+
files.push(...renderPages(reg, stack, templates));
|
|
6465
|
+
files.push({ rel: CONFIG_FILE, content: renderBrandConfig({ name: stack.title, ...DEFAULT_FIELDS }) });
|
|
6466
|
+
const ledger = { sdkVersion: SDK_VERSION, target: "components/g8", stack: stack.id, ...app ? { app } : {}, blocks: {} };
|
|
6467
|
+
files.push({ rel: "graph8.json", content: `${JSON.stringify(ledger, null, 2)}
|
|
6468
|
+
` });
|
|
6469
|
+
return files;
|
|
6470
|
+
}
|
|
6471
|
+
function dirProblem(abs, force) {
|
|
6472
|
+
if (!import_node_fs9.default.existsSync(abs)) return null;
|
|
6473
|
+
if (!import_node_fs9.default.statSync(abs).isDirectory()) return `${abs} exists and is not a directory.`;
|
|
6474
|
+
if (force || import_node_fs9.default.readdirSync(abs).length === 0) return null;
|
|
6475
|
+
return `${abs} is not empty. Pick a new directory, or pass --force to write into it (template files are overwritten).`;
|
|
6476
|
+
}
|
|
6477
|
+
function runCreate(given, reg, io, cwd) {
|
|
6478
|
+
let opts = given;
|
|
6479
|
+
if (io.orgBrand && given.orgBrand !== false) {
|
|
6480
|
+
const brand = mergeOrgBrand(given.brand, io.orgBrand, { name: given.title !== void 0 });
|
|
6481
|
+
if (hasBrandInput(brand)) opts = { ...given, brand };
|
|
6482
|
+
}
|
|
6483
|
+
let app;
|
|
6484
|
+
let stack = opts.blocks ? void 0 : reg.stacks.find((s) => s.id === opts.stack);
|
|
6485
|
+
if (opts.blocks) {
|
|
6486
|
+
let composed;
|
|
6487
|
+
try {
|
|
6488
|
+
composed = composeApp(reg, opts.blocks, { title: opts.title, withDepsScreens: opts.depsScreens === "with", noDepsScreens: opts.depsScreens === "none" });
|
|
6489
|
+
} catch (e) {
|
|
6490
|
+
io.err(e.message);
|
|
6491
|
+
return 1;
|
|
6492
|
+
}
|
|
6493
|
+
stack = composed.stack;
|
|
6494
|
+
app = { title: stack.title, blocks: [...new Set(opts.blocks)], screens: composed.screens.map((x) => ({ block: x.block, component: x.component, path: x.path })) };
|
|
6495
|
+
composed.warnings.forEach((w) => io.err(`warning: ${w}`));
|
|
6496
|
+
} else if (!stack) {
|
|
6497
|
+
io.err(`unknown stack "${opts.stack}". Stacks: ${reg.stacks.map((s) => s.id).join(", ") || "none"} (\`graph8-sdk list\`).`);
|
|
6498
|
+
return 1;
|
|
6499
|
+
}
|
|
6500
|
+
if (!stack) return 1;
|
|
6501
|
+
const name = opts.brand?.name?.trim();
|
|
6502
|
+
if (name) {
|
|
6503
|
+
stack = { ...stack, title: name };
|
|
6504
|
+
if (app) app = { ...app, title: name };
|
|
6505
|
+
}
|
|
6506
|
+
const problem = stackProblem(reg, stack);
|
|
6507
|
+
if (problem) return io.err(problem), 1;
|
|
6508
|
+
const abs = import_node_path10.default.resolve(cwd, opts.dir);
|
|
6509
|
+
const dirIssue = dirProblem(abs, opts.force);
|
|
6510
|
+
if (dirIssue) return io.err(dirIssue), 1;
|
|
6511
|
+
const brandIssue = opts.brand ? brandProblem(opts.brand, cwd) : null;
|
|
6512
|
+
if (brandIssue) return io.err(brandIssue), 1;
|
|
6513
|
+
const pm = opts.packageManager ?? packageManagerFromUserAgent(io.userAgent);
|
|
6514
|
+
let files;
|
|
6515
|
+
try {
|
|
6516
|
+
files = planCreate(reg, stack, appNameFor(opts.dir), pm, io, app);
|
|
6517
|
+
} catch (e) {
|
|
6518
|
+
io.err(e.message);
|
|
6519
|
+
return 1;
|
|
6520
|
+
}
|
|
6521
|
+
const blocks = resolveWithDeps(reg, stack.blocks);
|
|
6522
|
+
io.out(`${opts.dryRun ? "Dry run: " : ""}creating "${stack.title}" (${app ? `custom: ${app.blocks.join(", ")}` : stack.id}) in ${opts.dir}`);
|
|
6523
|
+
for (const f of files) io.out(`${opts.dryRun ? "would write" : "write"} ${f.rel}`);
|
|
6524
|
+
if (opts.dryRun) {
|
|
6525
|
+
io.out(`would add blocks: ${blocks.map((b) => b.id).join(", ")}`);
|
|
6526
|
+
if (opts.brand) io.out(`would brand the app: ${Object.entries(opts.brand).map(([k, v]) => `${k} ${v}`).join(", ")}`);
|
|
6527
|
+
io.out(opts.install ? `would run: ${pm} install` : "would not install dependencies (--no-install)");
|
|
6528
|
+
return 0;
|
|
6529
|
+
}
|
|
6530
|
+
for (const f of files) {
|
|
6531
|
+
const dest = import_node_path10.default.join(abs, f.rel);
|
|
6532
|
+
import_node_fs9.default.mkdirSync(import_node_path10.default.dirname(dest), { recursive: true });
|
|
6533
|
+
import_node_fs9.default.writeFileSync(dest, f.content, "utf8");
|
|
6534
|
+
}
|
|
6535
|
+
return finish(opts, reg, stack, blocks, abs, pm, io, cwd);
|
|
6536
|
+
}
|
|
6537
|
+
function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
|
|
6538
|
+
for (const b of blocks) {
|
|
6539
|
+
const code = runAdd({ spec: b.id, dir: abs, target: "components/g8", dryRun: false, force: opts.force, install: false, wireScreens: false }, reg, io, cwd);
|
|
6540
|
+
if (code !== 0) {
|
|
6541
|
+
io.err(`adding block "${b.id}" failed; the app in ${opts.dir} is incomplete.`);
|
|
6542
|
+
return code;
|
|
6543
|
+
}
|
|
6544
|
+
}
|
|
6545
|
+
if (opts.brand) {
|
|
6546
|
+
io.out("branding the app (graph8-sdk brand)");
|
|
6547
|
+
const code = runBrand({ ...opts.brand, dir: abs }, io, cwd, stack.title);
|
|
6548
|
+
if (code !== 0) {
|
|
6549
|
+
io.err(`branding failed; the app in ${opts.dir} is complete but unbranded. Fix the option and run \`npx @graph8/sdk brand\` in it.`);
|
|
6550
|
+
return code;
|
|
6551
|
+
}
|
|
6552
|
+
}
|
|
6553
|
+
if (opts.install) {
|
|
6554
|
+
io.out(`installing dependencies: ${pm} install`);
|
|
6555
|
+
const code = io.spawn(pm, ["install"], abs);
|
|
6556
|
+
if (code !== 0) {
|
|
6557
|
+
io.err(`dependency install failed (exit ${code}). Run it yourself: cd ${quoted(opts.dir)} && ${pm} install`);
|
|
6558
|
+
return 1;
|
|
6559
|
+
}
|
|
6560
|
+
}
|
|
6561
|
+
const cd = opts.dir === "." ? "" : `cd ${quoted(opts.dir)} && `;
|
|
6562
|
+
io.out(
|
|
6563
|
+
[
|
|
6564
|
+
"",
|
|
6565
|
+
`Done. Next:`,
|
|
6566
|
+
` 1. ${cd}${opts.install ? "" : `${pm} install && `}${pm === "npm" ? "npm run dev" : `${pm} dev`}`,
|
|
6567
|
+
` 2. open http://localhost:3000 and paste a graph8 API key in the Connect graph8 screen (local preview: only`,
|
|
6568
|
+
` this computer can reach the app's /api/g8 route), or set G8_API_KEY in ${import_node_path10.default.posix.join(opts.dir, ".env.local")}`,
|
|
6569
|
+
` 3. ${cd}npx @graph8/sdk doctor${opts.blocks ? "" : ` --stack ${stack.id}`} (before going live: add --production)`
|
|
6570
|
+
].join("\n")
|
|
6571
|
+
);
|
|
6572
|
+
return 0;
|
|
6573
|
+
}
|
|
6574
|
+
|
|
6451
6575
|
// src/cli.ts
|
|
6452
6576
|
var LIST_USAGE = `Usage: npx @graph8/sdk list [--json]
|
|
6453
6577
|
|
|
@@ -6471,7 +6595,7 @@ Run a command with --help for its options.
|
|
|
6471
6595
|
|
|
6472
6596
|
${INIT_AGENT_USAGE}`;
|
|
6473
6597
|
function registryFor(io) {
|
|
6474
|
-
const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ??
|
|
6598
|
+
const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ?? import_node_path11.default.resolve(__dirname, "..", "blocks");
|
|
6475
6599
|
return loadRegistry(root);
|
|
6476
6600
|
}
|
|
6477
6601
|
function defaultSpawn(cmd, args, cwd) {
|
|
@@ -6545,14 +6669,15 @@ function runCreateCommand(rest, io) {
|
|
|
6545
6669
|
if (wantsHelp(rest)) return io.out(CREATE_USAGE), 0;
|
|
6546
6670
|
try {
|
|
6547
6671
|
const opts = parseCreateArgs(rest);
|
|
6548
|
-
const packageRoot =
|
|
6672
|
+
const packageRoot = import_node_path11.default.resolve(__dirname, "..");
|
|
6549
6673
|
const cio = {
|
|
6550
6674
|
out: io.out,
|
|
6551
6675
|
err: io.err,
|
|
6552
6676
|
spawn: io.spawn ?? defaultSpawn,
|
|
6553
|
-
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ??
|
|
6554
|
-
sdkPackageJson:
|
|
6555
|
-
userAgent: io.userAgent ?? process.env.npm_config_user_agent
|
|
6677
|
+
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path11.default.join(packageRoot, "templates"),
|
|
6678
|
+
sdkPackageJson: import_node_path11.default.join(packageRoot, "package.json"),
|
|
6679
|
+
userAgent: io.userAgent ?? process.env.npm_config_user_agent,
|
|
6680
|
+
orgBrand: io.orgBrand
|
|
6556
6681
|
};
|
|
6557
6682
|
return runCreate(opts, registryFor(io), cio, io.cwd);
|
|
6558
6683
|
} catch (e) {
|
|
@@ -6563,13 +6688,13 @@ function runAddCommand(rest, io) {
|
|
|
6563
6688
|
if (wantsHelp(rest)) return io.out(ADD_USAGE), 0;
|
|
6564
6689
|
try {
|
|
6565
6690
|
const opts = parseAddArgs(rest);
|
|
6566
|
-
const packageRoot =
|
|
6691
|
+
const packageRoot = import_node_path11.default.resolve(__dirname, "..");
|
|
6567
6692
|
const aio = {
|
|
6568
6693
|
out: io.out,
|
|
6569
6694
|
err: io.err,
|
|
6570
6695
|
spawn: io.spawn ?? defaultSpawn,
|
|
6571
|
-
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ??
|
|
6572
|
-
sdkPackageJson:
|
|
6696
|
+
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path11.default.join(packageRoot, "templates"),
|
|
6697
|
+
sdkPackageJson: import_node_path11.default.join(packageRoot, "package.json")
|
|
6573
6698
|
};
|
|
6574
6699
|
return runAdd(opts, registryFor(io), aio, io.cwd);
|
|
6575
6700
|
} catch (e) {
|
|
@@ -6589,7 +6714,7 @@ function runCli(argv, io) {
|
|
|
6589
6714
|
if (command === "brand") {
|
|
6590
6715
|
if (wantsHelp(rest)) return io.out(BRAND_USAGE), 0;
|
|
6591
6716
|
try {
|
|
6592
|
-
return runBrand(parseBrandArgs(rest), { out: io.out, err: io.err }, io.cwd);
|
|
6717
|
+
return runBrand(parseBrandArgs(rest), { out: io.out, err: io.err }, io.cwd, void 0, io.orgBrand);
|
|
6593
6718
|
} catch (e) {
|
|
6594
6719
|
return fail(io, e.message);
|
|
6595
6720
|
}
|
|
@@ -6613,8 +6738,8 @@ ${USAGE}`);
|
|
|
6613
6738
|
return 1;
|
|
6614
6739
|
}
|
|
6615
6740
|
const read = (rel) => {
|
|
6616
|
-
const full =
|
|
6617
|
-
return
|
|
6741
|
+
const full = import_node_path11.default.join(io.cwd, rel);
|
|
6742
|
+
return import_node_fs10.default.existsSync(full) ? import_node_fs10.default.readFileSync(full, "utf8") : null;
|
|
6618
6743
|
};
|
|
6619
6744
|
let plans;
|
|
6620
6745
|
try {
|
|
@@ -6625,17 +6750,36 @@ ${USAGE}`);
|
|
|
6625
6750
|
}
|
|
6626
6751
|
for (const plan of plans) {
|
|
6627
6752
|
if (!options.dryRun && plan.action !== "unchanged") {
|
|
6628
|
-
const full =
|
|
6629
|
-
|
|
6630
|
-
|
|
6753
|
+
const full = import_node_path11.default.join(io.cwd, plan.path);
|
|
6754
|
+
import_node_fs10.default.mkdirSync(import_node_path11.default.dirname(full), { recursive: true });
|
|
6755
|
+
import_node_fs10.default.writeFileSync(full, plan.content, "utf8");
|
|
6631
6756
|
}
|
|
6632
6757
|
io.out(`${options.dryRun ? "would " : ""}${plan.action === "unchanged" ? "keep" : plan.action} ${plan.path} (${plan.target})`);
|
|
6633
6758
|
}
|
|
6634
6759
|
if (!options.dryRun) io.out("\nDone. The full guide is node_modules/@graph8/sdk/docs/agents.md.");
|
|
6635
6760
|
return 0;
|
|
6636
6761
|
}
|
|
6762
|
+
function brandRoot(command, rest, io) {
|
|
6763
|
+
const at = rest.findIndex((a) => a === "--dir" || a.startsWith("--dir="));
|
|
6764
|
+
const dir = at < 0 ? void 0 : rest[at].includes("=") ? rest[at].slice(6) : rest[at + 1];
|
|
6765
|
+
return command === "brand" && dir ? import_node_path11.default.resolve(io.cwd, dir) : io.cwd;
|
|
6766
|
+
}
|
|
6767
|
+
async function runWithOrgBrand(command, rest, argv, io) {
|
|
6768
|
+
const wants = !wantsHelp(rest) && !rest.includes("--no-org-brand") && (command === "brand" || !rest.includes("--dry-run")) && (command === "create" || rest.includes("--from-org") || !rest.some((a) => a.startsWith("--") && a !== "--dir" && !a.startsWith("--dir=")));
|
|
6769
|
+
if (!wants) return runCli(argv, io);
|
|
6770
|
+
const pull = await pullOrgBrand({ env: io.env ?? process.env, fetch: io.fetch }, brandRoot(command, rest, io));
|
|
6771
|
+
io.out(pull.info);
|
|
6772
|
+
const bare = command === "brand" && !rest.includes("--from-org") && !rest.some((a) => a.startsWith("--") && a !== "--dir" && !a.startsWith("--dir="));
|
|
6773
|
+
if (bare && !Object.keys(pull.values).length) return runCli(argv, io);
|
|
6774
|
+
try {
|
|
6775
|
+
return runCli(bare ? [...argv, "--from-org"] : argv, { ...io, orgBrand: pull.values });
|
|
6776
|
+
} finally {
|
|
6777
|
+
pull.cleanup();
|
|
6778
|
+
}
|
|
6779
|
+
}
|
|
6637
6780
|
async function runCliAsync(argv, io) {
|
|
6638
6781
|
const [command, ...rest] = argv;
|
|
6782
|
+
if (command === "create" || command === "brand") return runWithOrgBrand(command, rest, argv, io);
|
|
6639
6783
|
if (command !== "doctor") return runCli(argv, io);
|
|
6640
6784
|
if (wantsHelp(rest)) return io.out(DOCTOR_USAGE), 0;
|
|
6641
6785
|
try {
|