@graph8/sdk 0.370.0 → 0.374.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 +22 -3
- package/blocks/_foundation/README.md +61 -0
- package/blocks/_foundation/block.json +57 -0
- package/blocks/_foundation/components/auth-gate.tsx +363 -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/_foundation/server/auth.ts +815 -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/README.md +1 -1
- package/blocks/campaigns/block.json +34 -1
- package/blocks/campaigns/components/campaign-assets.tsx +1 -1
- package/blocks/campaigns/components/campaign-launch-step.tsx +16 -11
- package/blocks/campaigns/components/campaign-links.tsx +180 -0
- 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 +165 -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 +25 -0
- package/blocks/crm/block.json +257 -9
- package/blocks/crm/components/bulk-update.tsx +119 -0
- 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-documents-tab.tsx +144 -0
- package/blocks/crm/components/contact-insights-tab.tsx +118 -0
- package/blocks/crm/components/contact-lookup-link.tsx +85 -0
- package/blocks/crm/components/contact-row-actions.tsx +43 -0
- package/blocks/crm/components/contact-tag-chips.tsx +113 -0
- package/blocks/crm/components/contacts-screen.tsx +77 -18
- 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/owner-assign.tsx +132 -0
- package/blocks/crm/components/record-engagement-tab.tsx +103 -0
- package/blocks/crm/components/record-form.tsx +1 -1
- package/blocks/crm/components/record-open-links.tsx +79 -0
- package/blocks/crm/components/tag-assign.tsx +112 -0
- package/blocks/crm/components/tag-manager.tsx +178 -0
- package/blocks/crm/components/tasks-screen.tsx +35 -15
- package/blocks/crm/components/unlock-info-button.tsx +82 -0
- package/blocks/crm/components/verify-email-button.tsx +93 -0
- package/blocks/crm/hooks/use-all-lists.ts +18 -0
- package/blocks/crm/hooks/use-contact-tags.ts +29 -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/crm/lib/record-actions.ts +102 -0
- package/blocks/deals/README.md +25 -6
- package/blocks/deals/block.json +168 -2
- package/blocks/deals/components/close-deal-dialog.tsx +169 -0
- package/blocks/deals/components/create-deal-button.tsx +109 -0
- package/blocks/deals/components/deal-contacts-editor.tsx +134 -0
- package/blocks/deals/components/deal-detail.tsx +7 -5
- 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 +10 -2
- 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/documents/README.md +86 -0
- package/blocks/documents/block.json +378 -0
- package/blocks/documents/components/account-bar.tsx +48 -0
- package/blocks/documents/components/correspondence-form.tsx +95 -0
- package/blocks/documents/components/correspondence-import.tsx +106 -0
- package/blocks/documents/components/correspondence-panel.tsx +191 -0
- package/blocks/documents/components/documents-screen.tsx +76 -0
- package/blocks/documents/components/library-document-detail.tsx +175 -0
- package/blocks/documents/components/library-document-form.tsx +105 -0
- package/blocks/documents/components/library-folder-form.tsx +74 -0
- package/blocks/documents/components/library-panel.tsx +235 -0
- package/blocks/documents/components/library-upload.tsx +160 -0
- package/blocks/documents/components/question-row.tsx +130 -0
- package/blocks/documents/components/questionnaire-detail.tsx +197 -0
- package/blocks/documents/components/questionnaires-panel.tsx +173 -0
- package/blocks/documents/components/record-documents-tab.tsx +109 -0
- package/blocks/documents/lib/documents-types.ts +252 -0
- package/blocks/documents/lib/download.ts +64 -0
- package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
- package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
- package/blocks/documents/recipes/organize-the-library.ts +25 -0
- package/blocks/email-signatures/README.md +123 -0
- package/blocks/email-signatures/block.json +341 -0
- package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
- package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
- package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
- package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
- package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
- package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
- package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
- package/blocks/email-signatures/components/signature-policy.tsx +130 -0
- package/blocks/email-signatures/components/signature-preview.tsx +119 -0
- package/blocks/email-signatures/components/signature-report.tsx +77 -0
- package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
- package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
- package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
- package/blocks/email-signatures/lib/signatures-types.ts +392 -0
- package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
- package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
- package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
- package/blocks/enrichment/README.md +38 -10
- package/blocks/enrichment/block.json +164 -12
- package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
- package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +79 -30
- package/blocks/enrichment/components/email-verify-badge.tsx +3 -3
- package/blocks/enrichment/components/enrich-button.tsx +1 -1
- package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
- package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
- package/blocks/enrichment/components/enrichment-screen.tsx +103 -19
- package/blocks/enrichment/components/list-pipelines.tsx +97 -0
- package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
- package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
- package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
- package/blocks/enrichment/components/waterfall-config.tsx +27 -7
- package/blocks/enrichment/lib/enrichment-types.ts +51 -0
- package/blocks/enrichment/lib/pipeline-types.ts +234 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +15 -4
- package/blocks/forms/README.md +10 -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 +57 -20
- 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 +45 -4
- package/blocks/inbox/block.json +145 -8
- package/blocks/inbox/components/blocked-senders.tsx +103 -0
- package/blocks/inbox/components/compose-message.tsx +193 -0
- package/blocks/inbox/components/inbox-screen.tsx +54 -6
- package/blocks/inbox/components/inbox-search.tsx +106 -0
- package/blocks/inbox/components/record-conversations-tab.tsx +154 -0
- package/blocks/inbox/components/tag-manager.tsx +136 -0
- package/blocks/inbox/components/thread-actions.tsx +133 -0
- package/blocks/inbox/components/thread-badge.tsx +2 -2
- package/blocks/inbox/components/thread-view.tsx +3 -1
- package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
- package/blocks/inbox/lib/inbox-manage.ts +227 -0
- 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 +38 -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 +123 -2
- 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/hooks/use-landing-page-paths.ts +60 -0
- 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/programmatic-pages/README.md +82 -0
- package/blocks/programmatic-pages/block.json +292 -0
- package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
- package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
- package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
- package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
- package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
- package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
- package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
- package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
- package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
- package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
- package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
- package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
- package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
- package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
- package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
- package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
- package/blocks/quotes/README.md +38 -1
- package/blocks/quotes/block.json +170 -6
- package/blocks/quotes/components/create-quote-from-deal.tsx +48 -0
- package/blocks/quotes/components/manual-products.tsx +144 -0
- package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
- package/blocks/quotes/components/quote-builder.tsx +1 -1
- package/blocks/quotes/components/quote-documents.tsx +94 -0
- package/blocks/quotes/components/quote-evidence.tsx +95 -0
- package/blocks/quotes/components/quote-lifecycle-actions.tsx +146 -0
- package/blocks/quotes/components/quote-list.tsx +28 -8
- package/blocks/quotes/components/quote-related.tsx +3 -2
- package/blocks/quotes/components/quote-settings.tsx +184 -0
- package/blocks/quotes/components/quote-templates.tsx +189 -0
- package/blocks/quotes/components/quote-viewer.tsx +23 -5
- package/blocks/quotes/components/quotes-screen.tsx +15 -5
- package/blocks/quotes/components/record-quotes-tab.tsx +90 -0
- package/blocks/quotes/lib/quote-admin.ts +264 -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 +61 -0
- package/blocks/sequencer/block.json +200 -6
- package/blocks/sequencer/components/add-to-sequence-button.tsx +82 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +2 -2
- package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
- package/blocks/sequencer/components/record-sequences-tab.tsx +126 -0
- package/blocks/sequencer/components/sequence-builder.tsx +32 -23
- package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
- package/blocks/sequencer/components/sequence-detail.tsx +10 -2
- package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
- package/blocks/sequencer/components/sequence-enrollments.tsx +5 -2
- package/blocks/sequencer/components/sequence-list.tsx +1 -1
- package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
- package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
- package/blocks/sequencer/components/sequence-stats.tsx +8 -8
- package/blocks/sequencer/components/sequence-templates.tsx +160 -0
- package/blocks/sequencer/components/sequences-screen.tsx +39 -6
- package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
- package/blocks/sequencer/components/step-editor.tsx +21 -12
- package/blocks/sequencer/components/step-templates.tsx +224 -0
- package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
- package/blocks/sequencer/lib/sequencer-utils.ts +9 -5
- package/blocks/signals/README.md +39 -0
- package/blocks/signals/block.json +287 -0
- package/blocks/signals/components/auto-seed-panel.tsx +111 -0
- package/blocks/signals/components/listener-form.tsx +184 -0
- package/blocks/signals/components/matched-posts.tsx +195 -0
- package/blocks/signals/components/post-engagements.tsx +114 -0
- package/blocks/signals/components/record-social-tab.tsx +49 -0
- package/blocks/signals/components/social-listeners-screen.tsx +250 -0
- package/blocks/signals/components/social-settings.tsx +128 -0
- package/blocks/signals/components/source-lists.tsx +83 -0
- package/blocks/signals/components/top-engagers.tsx +68 -0
- package/blocks/signals/components/x-integration.tsx +129 -0
- package/blocks/signals/lib/signals-types.ts +228 -0
- package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
- package/blocks/signals/recipes/spend-against-caps.ts +14 -0
- package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
- 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 +7 -6
- package/blocks/visitors/components/visitor-company-card.tsx +24 -14
- package/blocks/visitors/components/visitor-feed.tsx +5 -5
- package/blocks/web-analytics/README.md +102 -0
- package/blocks/web-analytics/block.json +292 -0
- package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
- package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
- package/blocks/web-analytics/components/audience-tab.tsx +135 -0
- package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
- package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
- package/blocks/web-analytics/components/overview-tab.tsx +102 -0
- package/blocks/web-analytics/components/pages-tab.tsx +148 -0
- package/blocks/web-analytics/components/ranked-table.tsx +103 -0
- package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
- package/blocks/web-analytics/components/report-card.tsx +71 -0
- package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
- package/blocks/web-analytics/hooks/use-report.ts +48 -0
- package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
- package/blocks/web-analytics/lib/page-path.ts +14 -0
- package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
- package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
- package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
- package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
- package/blocks/webchat/components/chat-widget-config.tsx +1 -1
- package/blocks/webchat/components/conversation-list.tsx +1 -1
- package/blocks/workbench/README.md +54 -2
- package/blocks/workbench/block.json +234 -5
- package/blocks/workbench/components/column-recipe-editor.tsx +1 -1
- package/blocks/workbench/components/run-progress.tsx +1 -1
- package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
- package/blocks/workbench/components/workbench-automation.tsx +36 -0
- package/blocks/workbench/components/workbench-connections.tsx +136 -0
- package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
- package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
- package/blocks/workbench/components/workbench-grid.tsx +71 -8
- package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
- package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
- package/blocks/workbench/components/workbench-schedule.tsx +191 -0
- package/blocks/workbench/components/workbench-screen.tsx +65 -22
- package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
- package/blocks/workbench/components/workbench-table-list.tsx +14 -2
- package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
- package/blocks/workbench/components/workbench-triggers.tsx +178 -0
- package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
- package/blocks/workbench/lib/api-status.ts +8 -0
- package/blocks/workbench/lib/automation-types.ts +257 -0
- package/blocks/workflows/components/workflow-editor.tsx +1 -1
- package/dist/cli.js +744 -394
- 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/auth.d.ts +45 -0
- package/dist/types/blocks/brand.d.ts +11 -2
- package/dist/types/blocks/create.d.ts +11 -4
- 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/docs/api/forms.md +1 -1
- package/package.json +1 -1
- package/stacks/6sense/pages/companies.tsx.tmpl +21 -0
- package/stacks/6sense/pages/contacts.tsx.tmpl +21 -0
- package/stacks/6sense/pages/tasks.tsx.tmpl +19 -0
- package/stacks/6sense.json +37 -4
- package/stacks/apollo/pages/calls.tsx.tmpl +23 -0
- package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
- package/stacks/apollo/pages/companies.tsx.tmpl +19 -36
- package/stacks/apollo/pages/contacts.tsx.tmpl +35 -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 +24 -0
- package/stacks/apollo/pages/meetings.tsx.tmpl +13 -0
- package/stacks/apollo/pages/sequences.tsx.tmpl +18 -0
- package/stacks/apollo/pages/tasks.tsx.tmpl +20 -0
- package/stacks/apollo.json +68 -9
- 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 +14 -0
- package/stacks/clay/pages/lists.tsx.tmpl +24 -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 +34 -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 +30 -0
- package/stacks/hootsuite/pages/lists.tsx.tmpl +12 -0
- package/stacks/hootsuite/pages/tasks.tsx.tmpl +19 -0
- package/stacks/hootsuite.json +19 -4
- package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
- package/stacks/hubspot/pages/companies.tsx.tmpl +16 -35
- package/stacks/hubspot/pages/contacts.tsx.tmpl +35 -95
- package/stacks/hubspot/pages/deals.tsx.tmpl +14 -1
- package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
- 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 +18 -0
- package/stacks/hubspot/pages/tasks.tsx.tmpl +20 -0
- package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
- package/stacks/hubspot.json +66 -7
- 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 +18 -4
- package/stacks/mailchimp/pages/companies.tsx.tmpl +22 -0
- package/stacks/mailchimp/pages/contacts.tsx.tmpl +30 -0
- package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
- package/stacks/mailchimp/pages/lists.tsx.tmpl +12 -0
- package/stacks/mailchimp/pages/tasks.tsx.tmpl +19 -0
- package/stacks/mailchimp.json +22 -6
- package/stacks/outreach/pages/calls.tsx.tmpl +23 -0
- package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
- package/stacks/outreach/pages/companies.tsx.tmpl +22 -0
- package/stacks/outreach/pages/contacts.tsx.tmpl +29 -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 +18 -0
- package/stacks/outreach/pages/tasks.tsx.tmpl +19 -0
- package/stacks/outreach.json +38 -9
- package/stacks/pipedrive/pages/companies.tsx.tmpl +11 -35
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +17 -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 +14 -2
- package/stacks/salesforce/pages/companies.tsx.tmpl +16 -35
- package/stacks/salesforce/pages/contacts.tsx.tmpl +26 -35
- package/stacks/salesforce/pages/deals.tsx.tmpl +14 -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 +17 -3
- package/stacks/zoominfo/pages/companies.tsx.tmpl +17 -1
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +16 -0
- package/stacks/zoominfo/pages/lists.tsx.tmpl +24 -0
- package/stacks/zoominfo/pages/tasks.tsx.tmpl +19 -0
- package/stacks/zoominfo.json +36 -3
- package/styles/styles.css +1 -1
- package/templates/auth/auth-route.ts.tmpl +16 -0
- package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
- package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
- package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
- package/templates/auth/g8.auth.password.ts.tmpl +18 -0
- package/templates/auth/g8.auth.session.ts.tmpl +23 -0
- package/templates/auth/middleware.ts.tmpl +14 -0
- package/templates/next-app/README.md.tmpl +5 -0
- 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_fs11 = __toESM(require("fs"));
|
|
40
|
+
var import_node_path12 = __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, path13, next) => {
|
|
123
|
+
const current = read(path13);
|
|
124
|
+
plans.push({ target, path: path13, 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.374.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, path13, 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(/\/+$/, "") + path13 + 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);
|
|
@@ -6304,7 +5992,7 @@ async function runDoctor(opts, reg, io, cwd) {
|
|
|
6304
5992
|
await checkKey(call, blocks, key, line);
|
|
6305
5993
|
await checkSubscription(call, key, line);
|
|
6306
5994
|
}
|
|
6307
|
-
checkRoute(root, blocks, opts.production === true, io.out, line);
|
|
5995
|
+
checkRoute(root, blocks, opts.production === true, io.out, line, io.env);
|
|
6308
5996
|
if (opts.stack) printFlows(opts, reg, io.out, line);
|
|
6309
5997
|
else if (!opts.blocks.length) printAppScreens(root, reg, io.out);
|
|
6310
5998
|
printChecklist(blocks, io.out);
|
|
@@ -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,22 +6070,22 @@ 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
|
-
function checkRoute(root, blocks, production, out, line) {
|
|
6082
|
+
function checkRoute(root, blocks, production, out, line, env = {}) {
|
|
6395
6083
|
const rel = findRoute(root);
|
|
6396
6084
|
if (!rel) {
|
|
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}`);
|
|
@@ -6406,6 +6094,7 @@ Proxy route (${rel})${production ? ", production gate" : ""}:`);
|
|
|
6406
6094
|
facts.localPreview ? "`authorize` is not set: local preview (this computer, `next dev` only) is the only gate. Set `authorize` before going live; a production build refuses every call until you do" : "`authorize` is not set: the route refuses every call until you set it (check your app's session)"
|
|
6407
6095
|
);
|
|
6408
6096
|
} else line(true, "`authorize` is set");
|
|
6097
|
+
checkAuthConfig(root, problem, out, env);
|
|
6409
6098
|
if (facts.dangerouslyAllowUnauthenticated) problem("dangerouslyAllowUnauthenticated is on: anyone who can reach the route can use the key");
|
|
6410
6099
|
if (facts.dangerouslyAllowAllPaths) problem("dangerouslyAllowAllPaths is on: every /api/v1 area is reachable from the browser");
|
|
6411
6100
|
const need = derivePrefixes(blocks);
|
|
@@ -6421,6 +6110,21 @@ Proxy route (${rel})${production ? ", production gate" : ""}:`);
|
|
|
6421
6110
|
if (extra.length && blocks.length) out(`warn allowedPrefixes allows areas no installed block declares: ${extra.join(", ")}`);
|
|
6422
6111
|
}
|
|
6423
6112
|
}
|
|
6113
|
+
function checkAuthConfig(root, problem, out, env = {}) {
|
|
6114
|
+
const rel = ["g8.auth.ts", "src/g8.auth.ts"].find((f) => import_node_fs7.default.existsSync(import_node_path8.default.join(root, f)));
|
|
6115
|
+
if (!rel) return;
|
|
6116
|
+
const source = import_node_fs7.default.readFileSync(import_node_path8.default.join(root, rel), "utf8");
|
|
6117
|
+
const code = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'`])\/\/[^\n]*/g, "$1");
|
|
6118
|
+
if (/\ballowAnonymous\s*\(/.test(code)) {
|
|
6119
|
+
out(`warn ${rel} uses allowAnonymous(): NO sign-in. Anyone who can reach this app can use its graph8 API key through /api/g8. Use a narrowly scoped key with a spend cap, or switch to a sign-in preset`);
|
|
6120
|
+
}
|
|
6121
|
+
if (/\bjwtAuth\s*\(/.test(code) && !/\bclaims\s*:/.test(code) && !/\b(audience|secret)\s*:/.test(code)) {
|
|
6122
|
+
if (!readSetting("G8_JWT_AUDIENCE", root, env) && !readSetting("G8_JWT_SECRET", root, env)) {
|
|
6123
|
+
out(`warn ${rel} uses jwtAuth() with no audience: set G8_JWT_AUDIENCE (or a claims check), otherwise tokens your identity provider minted for other apps on the same tenant are accepted`);
|
|
6124
|
+
}
|
|
6125
|
+
}
|
|
6126
|
+
if (source.includes("graph8:auth-todo")) problem(`${rel} still has the sessionAuth stub (graph8:auth-todo): it refuses everyone until you wire your session library`);
|
|
6127
|
+
}
|
|
6424
6128
|
function fixPrefixes(root, blocks, io) {
|
|
6425
6129
|
const rel = findRoute(root);
|
|
6426
6130
|
if (!rel) {
|
|
@@ -6431,8 +6135,8 @@ function fixPrefixes(root, blocks, io) {
|
|
|
6431
6135
|
io.err("no blocks found: graph8.json lists none (or pass --blocks / --stack).");
|
|
6432
6136
|
return 1;
|
|
6433
6137
|
}
|
|
6434
|
-
const file =
|
|
6435
|
-
const source =
|
|
6138
|
+
const file = import_node_path8.default.join(root, rel);
|
|
6139
|
+
const source = import_node_fs7.default.readFileSync(file, "utf8");
|
|
6436
6140
|
const prefixes = derivePrefixes(blocks);
|
|
6437
6141
|
const next = applyPrefixes(source, prefixes) ?? upgradeLegacyRoute(source, prefixes);
|
|
6438
6142
|
if (next === null) {
|
|
@@ -6442,12 +6146,638 @@ function fixPrefixes(root, blocks, io) {
|
|
|
6442
6146
|
}
|
|
6443
6147
|
if (next === source) io.out(`keep ${rel} (allowedPrefixes already match the ${prefixes.length} areas of the installed blocks)`);
|
|
6444
6148
|
else {
|
|
6445
|
-
|
|
6149
|
+
import_node_fs7.default.writeFileSync(file, next, "utf8");
|
|
6446
6150
|
io.out(`update ${rel} (allowedPrefixes: ${prefixes.join(", ")})`);
|
|
6447
6151
|
}
|
|
6448
6152
|
return 0;
|
|
6449
6153
|
}
|
|
6450
6154
|
|
|
6155
|
+
// src/blocks/orgBrand.ts
|
|
6156
|
+
var TIMEOUT_MS = 8e3;
|
|
6157
|
+
var NONE = (info) => ({ values: {}, info, cleanup: () => {
|
|
6158
|
+
} });
|
|
6159
|
+
function colorOf(v) {
|
|
6160
|
+
const raw = typeof v === "string" ? v : v && typeof v === "object" ? v.value : null;
|
|
6161
|
+
return typeof raw === "string" ? normalizeHex(raw) : null;
|
|
6162
|
+
}
|
|
6163
|
+
function pickColor(tokens) {
|
|
6164
|
+
const t = tokens ?? {};
|
|
6165
|
+
for (const k of ["primary", "brand", "accent"]) {
|
|
6166
|
+
const c = colorOf(t.colors?.[k]);
|
|
6167
|
+
if (c) return c;
|
|
6168
|
+
}
|
|
6169
|
+
return colorOf(t.components?.button?.background);
|
|
6170
|
+
}
|
|
6171
|
+
function cleanName(v) {
|
|
6172
|
+
if (typeof v !== "string") return null;
|
|
6173
|
+
const s = v.replace(/[\u0000-\u001f\u007f<>`$\\]/g, "").replace(/\s+/g, " ").trim().slice(0, 60);
|
|
6174
|
+
return s || null;
|
|
6175
|
+
}
|
|
6176
|
+
var MIME = { "image/svg+xml": "svg", "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif" };
|
|
6177
|
+
function privateHost(host) {
|
|
6178
|
+
const h = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
6179
|
+
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);
|
|
6180
|
+
}
|
|
6181
|
+
async function downloadLogo(url, f) {
|
|
6182
|
+
let bytes;
|
|
6183
|
+
let ext;
|
|
6184
|
+
if (url.startsWith("data:")) {
|
|
6185
|
+
const m = /^data:([^;,]+)(;base64)?,(.*)$/s.exec(url);
|
|
6186
|
+
if (!m) return null;
|
|
6187
|
+
ext = MIME[m[1].toLowerCase()];
|
|
6188
|
+
bytes = m[2] ? Buffer.from(m[3], "base64") : Buffer.from(decodeURIComponent(m[3]));
|
|
6189
|
+
} else {
|
|
6190
|
+
const u = new URL(url);
|
|
6191
|
+
if (u.protocol !== "https:" || privateHost(u.hostname)) return null;
|
|
6192
|
+
const res = await f(u, { redirect: "error", signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
6193
|
+
if (!res.ok) return null;
|
|
6194
|
+
const type = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
6195
|
+
ext = MIME[type] ?? (type && type !== "application/octet-stream" ? void 0 : LOGO_TYPES.find((e) => u.pathname.toLowerCase().endsWith(`.${e}`)));
|
|
6196
|
+
bytes = Buffer.from(await res.arrayBuffer());
|
|
6197
|
+
}
|
|
6198
|
+
if (!ext || !LOGO_TYPES.includes(ext) || !bytes.length || bytes.length > MAX_LOGO_BYTES) return null;
|
|
6199
|
+
const dir = import_node_fs8.default.mkdtempSync(import_node_path9.default.join(import_node_os.default.tmpdir(), "g8-org-logo-"));
|
|
6200
|
+
const file = import_node_path9.default.join(dir, `logo.${ext}`);
|
|
6201
|
+
import_node_fs8.default.writeFileSync(file, bytes);
|
|
6202
|
+
return { file, dir };
|
|
6203
|
+
}
|
|
6204
|
+
async function tryCall(call, op) {
|
|
6205
|
+
try {
|
|
6206
|
+
return { data: (await call(op))?.data };
|
|
6207
|
+
} catch (e) {
|
|
6208
|
+
return { status: e.status ?? 0 };
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
async function pullOrgBrand(io, root) {
|
|
6212
|
+
try {
|
|
6213
|
+
const key = readSetting("G8_API_KEY", root, io.env);
|
|
6214
|
+
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)");
|
|
6215
|
+
const api = createApiClient(key, readSetting("G8_API_URL", root, io.env));
|
|
6216
|
+
const f = io.fetch ?? fetch;
|
|
6217
|
+
const call = (op) => api.call(op, void 0, { fetchImpl: (u, i) => f(u, { ...i, signal: AbortSignal.timeout(TIMEOUT_MS) }), maxRetries: 0 });
|
|
6218
|
+
const [kit, me] = await Promise.all([tryCall(call, "get_brand_kit_overview_brand_kit_get"), tryCall(call, "describe_current_key_me_get")]);
|
|
6219
|
+
if (!kit.data) return NONE(`org brand: the Brand Kit could not be read (${kit.status ? `HTTP ${kit.status}` : "unreachable"}), using the default look`);
|
|
6220
|
+
const values = {};
|
|
6221
|
+
const color = pickColor(kit.data.design_tokens);
|
|
6222
|
+
if (color) values.color = color;
|
|
6223
|
+
const name = cleanName(me.data?.org_name);
|
|
6224
|
+
if (name) values.name = name;
|
|
6225
|
+
let dir = null;
|
|
6226
|
+
const logoUrl = typeof kit.data.logo_url === "string" ? kit.data.logo_url.trim() : "";
|
|
6227
|
+
if (logoUrl) {
|
|
6228
|
+
try {
|
|
6229
|
+
const dl = await downloadLogo(logoUrl, f);
|
|
6230
|
+
if (dl) values.logo = dl.file, dir = dl.dir;
|
|
6231
|
+
} catch {
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
const got = [values.name && "name", values.color && "colour", values.logo && "logo"].filter(Boolean);
|
|
6235
|
+
const cleanup = () => dir && import_node_fs8.default.rmSync(dir, { recursive: true, force: true });
|
|
6236
|
+
if (!got.length) return NONE("org brand: the Brand Kit is empty, using the default look");
|
|
6237
|
+
return { values, info: `org brand: using your Brand Kit (${got.join(", ")}); explicit flags win, --no-org-brand skips this`, cleanup };
|
|
6238
|
+
} catch {
|
|
6239
|
+
return NONE("org brand: could not reach the Brand Kit, using the default look");
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
|
|
6243
|
+
// src/blocks/create.ts
|
|
6244
|
+
var import_node_fs10 = __toESM(require("fs"));
|
|
6245
|
+
var import_node_path11 = __toESM(require("path"));
|
|
6246
|
+
|
|
6247
|
+
// src/blocks/auth.ts
|
|
6248
|
+
var import_node_crypto2 = require("crypto");
|
|
6249
|
+
var import_node_fs9 = __toESM(require("fs"));
|
|
6250
|
+
var import_node_path10 = __toESM(require("path"));
|
|
6251
|
+
var AUTH_PRESETS = ["password", "basic", "jwt", "session", "anonymous"];
|
|
6252
|
+
var AUTH_CONFIG_FILE = "g8.auth.ts";
|
|
6253
|
+
var AUTH_ROUTE_FILE = "app/api/g8-auth/route.ts";
|
|
6254
|
+
function isAuthPreset(v) {
|
|
6255
|
+
return AUTH_PRESETS.includes(v);
|
|
6256
|
+
}
|
|
6257
|
+
function usesSignInScreen(p) {
|
|
6258
|
+
return p === "password" || p === "jwt" || p === "session";
|
|
6259
|
+
}
|
|
6260
|
+
var SUMMARY = {
|
|
6261
|
+
password: "one shared team password, a signed HttpOnly session cookie and a sign-in screen",
|
|
6262
|
+
basic: "HTTP Basic auth: the browser asks for a user and password on every page",
|
|
6263
|
+
jwt: "your identity provider's JWT (OIDC: Auth0, Okta, Cognito, Keycloak, Clerk...), checked against its keys",
|
|
6264
|
+
session: "your own session library (Clerk, NextAuth, Supabase...): wire one function in g8.auth.ts",
|
|
6265
|
+
anonymous: "NO sign-in: anyone who can reach the app can use its key (public demos only)"
|
|
6266
|
+
};
|
|
6267
|
+
function authSummary(p) {
|
|
6268
|
+
return SUMMARY[p];
|
|
6269
|
+
}
|
|
6270
|
+
var ENV = {
|
|
6271
|
+
password: [
|
|
6272
|
+
"# Sign-in (create --auth password): the team password (12+ characters) and the cookie signing secret",
|
|
6273
|
+
"# (32+ random characters: openssl rand -base64 48). create wrote fresh values to .env.local.",
|
|
6274
|
+
"G8_APP_PASSWORD=",
|
|
6275
|
+
"G8_SESSION_SECRET="
|
|
6276
|
+
],
|
|
6277
|
+
basic: ["# Sign-in (create --auth basic): user:password (12+ character passwords, no commas), comma-separated for several users.", "G8_BASIC_AUTH="],
|
|
6278
|
+
jwt: [
|
|
6279
|
+
"# Sign-in (create --auth jwt): your identity provider. See g8.auth.ts for examples.",
|
|
6280
|
+
"G8_JWT_ISSUER=",
|
|
6281
|
+
"G8_JWT_JWKS_URL=",
|
|
6282
|
+
"G8_JWT_AUDIENCE=",
|
|
6283
|
+
"G8_JWT_COOKIE=",
|
|
6284
|
+
"# Or an HS256 secret instead of a JWKS (32+ characters):",
|
|
6285
|
+
"G8_JWT_SECRET=",
|
|
6286
|
+
"G8_SIGN_IN_URL=",
|
|
6287
|
+
"G8_SIGN_OUT_URL="
|
|
6288
|
+
],
|
|
6289
|
+
session: ["# Sign-in (create --auth session): where the sign-in screen sends people, and the Sign out link.", "G8_SIGN_IN_URL=", "G8_SIGN_OUT_URL="],
|
|
6290
|
+
anonymous: []
|
|
6291
|
+
};
|
|
6292
|
+
function authEnvLines(p) {
|
|
6293
|
+
return ENV[p];
|
|
6294
|
+
}
|
|
6295
|
+
function planAuthFiles(p, templatesRoot) {
|
|
6296
|
+
const dir = import_node_path10.default.join(templatesRoot, "auth");
|
|
6297
|
+
const read = (f) => render(import_node_fs9.default.readFileSync(import_node_path10.default.join(dir, f), "utf8"), { preset: p });
|
|
6298
|
+
const files = [{ rel: AUTH_CONFIG_FILE, content: read(`g8.auth.${p}.ts.tmpl`) }];
|
|
6299
|
+
if (usesSignInScreen(p)) files.push({ rel: AUTH_ROUTE_FILE, content: read("auth-route.ts.tmpl") });
|
|
6300
|
+
if (p === "basic") files.push({ rel: "middleware.ts", content: read("middleware.ts.tmpl") });
|
|
6301
|
+
return files;
|
|
6302
|
+
}
|
|
6303
|
+
var AUTHORIZE_HINT = /^[ \t]*\/\/ authorize: async \(request\) => \{[^\n]*\n/m;
|
|
6304
|
+
var LAST_IMPORT = /^import [^\n]+;\n(?![\s\S]*^import )/m;
|
|
6305
|
+
function applyAuthToRoute(source) {
|
|
6306
|
+
if (!AUTHORIZE_HINT.test(source) || !LAST_IMPORT.test(source) || source.includes('from "@/g8.auth"')) return null;
|
|
6307
|
+
return source.replace(LAST_IMPORT, (m) => `${m}import { appAuth } from "@/g8.auth";
|
|
6308
|
+
`).replace(AUTHORIZE_HINT, " authorize: appAuth.authorize, // g8.auth.ts: who may use this route (npx @graph8/sdk create --auth)\n");
|
|
6309
|
+
}
|
|
6310
|
+
var LAYOUT_IMPORT = 'import { G8ConnectGate } from "@/components/g8/_foundation/connect-graph8";\n';
|
|
6311
|
+
var LAYOUT_BODY = /^([ \t]*)<AppShell>\n([\s\S]*?)^\1<\/AppShell>\n/m;
|
|
6312
|
+
function applyAuthToLayout(source) {
|
|
6313
|
+
const m = LAYOUT_BODY.exec(source);
|
|
6314
|
+
if (!m || !source.includes(LAYOUT_IMPORT)) return null;
|
|
6315
|
+
const pad = m[1];
|
|
6316
|
+
const inner = m[2].split("\n").map((l) => l ? ` ${l}` : l).join("\n");
|
|
6317
|
+
const body = [
|
|
6318
|
+
`${pad}{/* Sign-in (g8.auth.ts): the sign-in screen until the visitor is signed in. The proxy enforces it on the server. */}`,
|
|
6319
|
+
`${pad}<BrandProvider brand={brand}>`,
|
|
6320
|
+
`${pad} <G8AuthGate>`,
|
|
6321
|
+
`${pad} <AppShell headerSlot={<G8SignOutButton />}>`,
|
|
6322
|
+
`${inner}${pad} </AppShell>`,
|
|
6323
|
+
`${pad} </G8AuthGate>`,
|
|
6324
|
+
`${pad}</BrandProvider>`,
|
|
6325
|
+
""
|
|
6326
|
+
].join("\n");
|
|
6327
|
+
const imports = `${LAYOUT_IMPORT}import { G8AuthGate, G8SignOutButton } from "@/components/g8/_foundation/auth-gate";
|
|
6328
|
+
import { BrandProvider } from "@/components/g8/_foundation/brand-provider";
|
|
6329
|
+
`;
|
|
6330
|
+
return source.replace(LAYOUT_IMPORT, imports).replace(LAYOUT_BODY, body);
|
|
6331
|
+
}
|
|
6332
|
+
var GO_LIVE_ITEM = /^- \[ \] Set `authorize` in `app\/api\/g8\/\[\.\.\.path\]\/route\.ts`[^\n]*\n(?: {6}[^\n]*\n)*/m;
|
|
6333
|
+
function readmeSection(p) {
|
|
6334
|
+
const vars2 = authEnvLines(p).filter((l) => !l.startsWith("#")).map((l) => `\`${l.replace(/=$/, "")}\``);
|
|
6335
|
+
const lines = [
|
|
6336
|
+
"## Sign-in",
|
|
6337
|
+
"",
|
|
6338
|
+
`This app was created with \`--auth ${p}\`: ${authSummary(p)}. \`g8.auth.ts\` holds the preset, and`,
|
|
6339
|
+
"`app/api/g8/[...path]/route.ts` runs its `authorize` before it forwards any call with the server's graph8 API key.",
|
|
6340
|
+
""
|
|
6341
|
+
];
|
|
6342
|
+
if (vars2.length) lines.push(`Server environment (\`.env.local\` while developing, your host's settings in production): ${vars2.join(", ")}.`, "");
|
|
6343
|
+
if (p === "password") lines.push("`create` wrote a fresh team password and session secret to `.env.local` (git-ignored): read the password there, and set your own values on the host before deploying.", "");
|
|
6344
|
+
if (p === "basic") lines.push("`create` wrote a fresh `admin:<random>` login to `.env.local` (git-ignored). `middleware.ts` makes the browser ask for it on every page. Serve the app over HTTPS only.", "");
|
|
6345
|
+
if (p === "jwt") lines.push("Set the issuer (or JWKS URL), the audience and the cookie your provider uses; examples are in `g8.auth.ts`.", "");
|
|
6346
|
+
if (p === "session") lines.push("Wire your session library in `g8.auth.ts` (Clerk, NextAuth and Supabase examples inside). Until then it refuses everyone and `doctor --production` fails.", "");
|
|
6347
|
+
if (p === "anonymous") lines.push("**Warning: no sign-in.** Anyone who can reach the app can use its key through `/api/g8`. Use a narrowly scoped key with a spend cap, or switch preset in `g8.auth.ts`.", "");
|
|
6348
|
+
lines.push('Switch preset or tune it (session length, extra JWT claim checks): `components/g8/_foundation/README.md`, "Sign-in presets".', "", "");
|
|
6349
|
+
return lines.join("\n");
|
|
6350
|
+
}
|
|
6351
|
+
function applyAuthToReadme(source, p) {
|
|
6352
|
+
if (!source.includes("\n## Go live\n") || !GO_LIVE_ITEM.test(source)) return null;
|
|
6353
|
+
const item = p === "anonymous" ? "- [ ] `authorize` is set by `--auth anonymous`: NO sign-in. Make sure that is what you want (see Sign-in above).\n" : `- [x] \`authorize\` is set by the \`--auth ${p}\` preset (\`g8.auth.ts\`). Set its variables on your host (see Sign-in above).
|
|
6354
|
+
`;
|
|
6355
|
+
return source.replace("\n## Go live\n", `
|
|
6356
|
+
${readmeSection(p)}## Go live
|
|
6357
|
+
`).replace(GO_LIVE_ITEM, item);
|
|
6358
|
+
}
|
|
6359
|
+
function applyAuthToEnvExample(source, p) {
|
|
6360
|
+
const lines = authEnvLines(p);
|
|
6361
|
+
return lines.length ? `${source.replace(/\n*$/, "\n")}${lines.join("\n")}
|
|
6362
|
+
` : source;
|
|
6363
|
+
}
|
|
6364
|
+
var secret = (bytes) => (0, import_node_crypto2.randomBytes)(bytes).toString("base64url");
|
|
6365
|
+
function writeAuthSecrets(root, p) {
|
|
6366
|
+
const wanted = p === "password" ? { G8_APP_PASSWORD: secret(18), G8_SESSION_SECRET: secret(48) } : p === "basic" ? { G8_BASIC_AUTH: `admin:${secret(18)}` } : {};
|
|
6367
|
+
const file = import_node_path10.default.join(root, ".env.local");
|
|
6368
|
+
const current = import_node_fs9.default.existsSync(file) ? import_node_fs9.default.readFileSync(file, "utf8") : "";
|
|
6369
|
+
const has = (name) => new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=\\s*\\S`, "m").test(current);
|
|
6370
|
+
const add = Object.entries(wanted).filter(([name]) => !has(name));
|
|
6371
|
+
if (!add.length) return [];
|
|
6372
|
+
const block = [`# Written by \`npx @graph8/sdk create --auth ${p}\` for local development. Set your own values on your host.`, ...add.map(([k, v]) => `${k}=${v}`)].join("\n");
|
|
6373
|
+
import_node_fs9.default.writeFileSync(file, `${current ? `${current.replace(/\n*$/, "\n")}
|
|
6374
|
+
` : ""}${block}
|
|
6375
|
+
`, { encoding: "utf8", mode: 384 });
|
|
6376
|
+
return add.map(([k]) => k);
|
|
6377
|
+
}
|
|
6378
|
+
|
|
6379
|
+
// src/blocks/create.ts
|
|
6380
|
+
var CREATE_USAGE = `Usage: npx @graph8/sdk create <stack> <dir> [options]
|
|
6381
|
+
|
|
6382
|
+
Scaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and
|
|
6383
|
+
install the stack's blocks into it. Run \`graph8-sdk list\` to see the stacks.
|
|
6384
|
+
--package-manager <npm|pnpm|yarn|bun> default: the one running the command, else npm
|
|
6385
|
+
--no-install write the files and blocks, do not install dependencies
|
|
6386
|
+
--dry-run print what would be written, write and install nothing
|
|
6387
|
+
--force allow a non-empty <dir> (template files are overwritten, and graph8.json
|
|
6388
|
+
is reset: blocks you added to that app earlier are no longer recorded)
|
|
6389
|
+
--auth <preset> scaffold sign-in so the app can go live (sets \`authorize\` on the proxy route):
|
|
6390
|
+
password one shared team password, session cookie, sign-in screen
|
|
6391
|
+
basic HTTP Basic auth on every page (the browser asks)
|
|
6392
|
+
jwt your identity provider's JWT (OIDC JWKS or an HS256 secret)
|
|
6393
|
+
session your own session library (Clerk, NextAuth, Supabase): wire g8.auth.ts
|
|
6394
|
+
anonymous NO sign-in: anyone who can reach the app uses its key (public demos)
|
|
6395
|
+
Without --auth: local preview only, and a production build refuses /api/g8 until you
|
|
6396
|
+
set \`authorize\` yourself (unchanged).
|
|
6397
|
+
Then: \`npm run dev\` in <dir>, open http://localhost:3000 and paste a graph8 API key (local preview), and run
|
|
6398
|
+
\`npx @graph8/sdk doctor --stack <stack>\` (add --production before going live).
|
|
6399
|
+
|
|
6400
|
+
Or pick the features yourself, for any app (also: \`create custom --blocks ...\`):
|
|
6401
|
+
npx @graph8/sdk create --blocks <id,id,...> [--title "Acme CRM"] <dir> [options]
|
|
6402
|
+
Every requested block's dependencies come with it. The app gets one route and one sidebar entry per finished
|
|
6403
|
+
Screen of the requested blocks, plus the user-facing Screens of their dependencies (asking for deals also gives
|
|
6404
|
+
you crm's screens). \`/\` opens the analytics screen if you asked for one, else the first screen.
|
|
6405
|
+
--title <text> the app's name (default: graph8 app)
|
|
6406
|
+
--with-deps-screens also add the plain fallback page of dependency blocks that have no Screen
|
|
6407
|
+
--no-deps-screens add screens only for the blocks you named
|
|
6408
|
+
Run \`graph8-sdk list\` to see each block and which have Screens.
|
|
6409
|
+
|
|
6410
|
+
Brand it (any stack or --blocks; the same as running \`graph8-sdk brand\` in the new app afterwards):
|
|
6411
|
+
--name <text> the app's name (with --blocks, the same as --title)
|
|
6412
|
+
--brand-color <hex> primary colour, AA-checked; --dark-brand-color <hex> for dark mode
|
|
6413
|
+
--logo <path> svg/png/jpg/webp/gif, shown in the sidebar
|
|
6414
|
+
--app-url <url> white-label: where "Open in graph8" links go; --link-label <text>: what they call it
|
|
6415
|
+
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
|
|
6416
|
+
pass: the app name, the primary colour and the logo (downloaded into public/brand/). No key, no kit or an error:
|
|
6417
|
+
the default look, one info line, never a failure. --no-org-brand skips it.
|
|
6418
|
+
A directory literally named \`custom\` would read as the alias: pass \`--blocks crm ./custom\`.`;
|
|
6419
|
+
var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
6420
|
+
function parseCreateArgs(argv) {
|
|
6421
|
+
const o = { stack: "", dir: "", install: true, dryRun: false, force: false };
|
|
6422
|
+
const positional = [];
|
|
6423
|
+
for (let i = 0; i < argv.length; i++) {
|
|
6424
|
+
const arg = argv[i];
|
|
6425
|
+
const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
|
|
6426
|
+
const [flag, inline] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [arg, void 0];
|
|
6427
|
+
if (flag === "--package-manager") {
|
|
6428
|
+
const v = inline ?? argv[++i];
|
|
6429
|
+
if (!PACKAGE_MANAGERS.includes(v)) throw new Error(`--package-manager must be one of ${PACKAGE_MANAGERS.join(", ")}
|
|
6430
|
+
|
|
6431
|
+
${CREATE_USAGE}`);
|
|
6432
|
+
o.packageManager = v;
|
|
6433
|
+
} else if (flag === "--blocks") {
|
|
6434
|
+
const v = inline ?? argv[++i];
|
|
6435
|
+
if (v === void 0 || v.startsWith("--")) throw new Error(`--blocks needs a comma-separated list of block ids
|
|
6436
|
+
|
|
6437
|
+
${CREATE_USAGE}`);
|
|
6438
|
+
o.blocks = [...o.blocks ?? [], ...v.split(",").map((x) => x.trim()).filter(Boolean)];
|
|
6439
|
+
} else if (flag === "--auth") {
|
|
6440
|
+
const v = inline ?? argv[++i];
|
|
6441
|
+
if (v === void 0 || !isAuthPreset(v)) throw new Error(`--auth must be one of ${AUTH_PRESETS.join(", ")}
|
|
6442
|
+
|
|
6443
|
+
${CREATE_USAGE}`);
|
|
6444
|
+
o.auth = v;
|
|
6445
|
+
} else if (flag === "--title") {
|
|
6446
|
+
const v = inline ?? argv[++i];
|
|
6447
|
+
if (v === void 0 || v.startsWith("--")) throw new Error(`--title needs a value
|
|
6448
|
+
|
|
6449
|
+
${CREATE_USAGE}`);
|
|
6450
|
+
o.title = v;
|
|
6451
|
+
} else if (takeBrandFlag(o.brand ??= {}, flag, () => {
|
|
6452
|
+
const v = inline ?? argv[++i];
|
|
6453
|
+
if (v === void 0 || v.startsWith("--")) throw new Error(`${flag} needs a value
|
|
6454
|
+
|
|
6455
|
+
${CREATE_USAGE}`);
|
|
6456
|
+
return v;
|
|
6457
|
+
})) {
|
|
6458
|
+
} else if (arg === "--no-org-brand") o.orgBrand = false;
|
|
6459
|
+
else if (arg === "--with-deps-screens") o.depsScreens = "with";
|
|
6460
|
+
else if (arg === "--no-deps-screens") o.depsScreens = "none";
|
|
6461
|
+
else if (arg === "--no-install") o.install = false;
|
|
6462
|
+
else if (arg === "--dry-run") o.dryRun = true;
|
|
6463
|
+
else if (arg === "--force") o.force = true;
|
|
6464
|
+
else if (arg.startsWith("-")) throw new Error(`unknown option ${arg}
|
|
6465
|
+
|
|
6466
|
+
${CREATE_USAGE}`);
|
|
6467
|
+
else positional.push(arg);
|
|
6468
|
+
}
|
|
6469
|
+
if (o.brand && !hasBrandInput(o.brand)) delete o.brand;
|
|
6470
|
+
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
|
|
6471
|
+
|
|
6472
|
+
${CREATE_USAGE}`);
|
|
6473
|
+
if (o.blocks || positional[0] === "custom") return parseCustom(o, positional);
|
|
6474
|
+
if (o.title || o.depsScreens) throw new Error(`--title, --with-deps-screens and --no-deps-screens go with --blocks
|
|
6475
|
+
|
|
6476
|
+
${CREATE_USAGE}`);
|
|
6477
|
+
if (positional.length !== 2) throw new Error(`create needs a stack and a directory
|
|
6478
|
+
|
|
6479
|
+
${CREATE_USAGE}`);
|
|
6480
|
+
[o.stack, o.dir] = positional;
|
|
6481
|
+
return o;
|
|
6482
|
+
}
|
|
6483
|
+
function parseCustom(o, positional) {
|
|
6484
|
+
const rest = positional[0] === "custom" ? positional.slice(1) : positional;
|
|
6485
|
+
if (!o.blocks?.length) throw new Error(`create custom needs --blocks <id,id,...> (see \`graph8-sdk list\`)
|
|
6486
|
+
|
|
6487
|
+
${CREATE_USAGE}`);
|
|
6488
|
+
if (rest.length !== 1) {
|
|
6489
|
+
throw new Error(`${rest.length ? "a stack name cannot be combined with --blocks; pass only the directory" : "create --blocks needs a directory"}
|
|
6490
|
+
|
|
6491
|
+
${CREATE_USAGE}`);
|
|
6492
|
+
}
|
|
6493
|
+
return { ...o, stack: "custom", dir: rest[0] };
|
|
6494
|
+
}
|
|
6495
|
+
function packageManagerFromUserAgent(ua) {
|
|
6496
|
+
const name = /^(npm|pnpm|yarn|bun)\//.exec(ua ?? "")?.[1];
|
|
6497
|
+
return name ?? "npm";
|
|
6498
|
+
}
|
|
6499
|
+
function appNameFor(dir) {
|
|
6500
|
+
const base = import_node_path11.default.basename(import_node_path11.default.resolve(dir)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|-+$/g, "");
|
|
6501
|
+
return base || "g8-app";
|
|
6502
|
+
}
|
|
6503
|
+
var quoted = (p) => /^[\w@%+=:,./-]+$/.test(p) ? p : JSON.stringify(p);
|
|
6504
|
+
function walk(dir, base = dir) {
|
|
6505
|
+
const out = [];
|
|
6506
|
+
for (const e of import_node_fs10.default.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
6507
|
+
const full = import_node_path11.default.join(dir, e.name);
|
|
6508
|
+
if (e.isDirectory()) out.push(...walk(full, base));
|
|
6509
|
+
else out.push(posix(import_node_path11.default.relative(base, full)));
|
|
6510
|
+
}
|
|
6511
|
+
return out;
|
|
6512
|
+
}
|
|
6513
|
+
function outputName(rel) {
|
|
6514
|
+
const noExt = rel.replace(/\.tmpl$/, "");
|
|
6515
|
+
const dir = import_node_path11.default.posix.dirname(noExt);
|
|
6516
|
+
const base = import_node_path11.default.posix.basename(noExt).replace(/^dot-/, ".");
|
|
6517
|
+
return dir === "." ? base : `${dir}/${base}`;
|
|
6518
|
+
}
|
|
6519
|
+
function stackProblem(reg, stack) {
|
|
6520
|
+
const { missing } = stackStatus(reg, stack);
|
|
6521
|
+
if (missing.length) {
|
|
6522
|
+
const ready = reg.stacks.filter((s) => !stackStatus(reg, s).missing.length).map((s) => s.id);
|
|
6523
|
+
return [
|
|
6524
|
+
`stack "${stack.id}" is planned: these blocks are not in this version of @graph8/sdk yet: ${missing.join(", ")}.`,
|
|
6525
|
+
`Stacks you can create now: ${ready.join(", ") || "none"}. Or create one of those and add blocks as they ship (\`graph8-sdk add <block>\`).`
|
|
6526
|
+
].join("\n");
|
|
6527
|
+
}
|
|
6528
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6529
|
+
const homeIssue = homeProblem(stack);
|
|
6530
|
+
if (homeIssue) return homeIssue;
|
|
6531
|
+
for (const n of stack.nav) {
|
|
6532
|
+
const block = findBlock(reg, n.block);
|
|
6533
|
+
if (!stack.blocks.includes(n.block)) return `stack "${stack.id}" nav "${n.label}" uses block "${n.block}" which the stack does not include.`;
|
|
6534
|
+
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.`;
|
|
6535
|
+
if (/[\[\]]/.test(n.path)) return `stack "${stack.id}" nav "${n.label}" uses ${n.path}: dynamic segments cannot be a sidebar link.`;
|
|
6536
|
+
if (seen.has(n.path)) return `stack "${stack.id}" has two nav entries at ${n.path}.`;
|
|
6537
|
+
seen.add(n.path);
|
|
6538
|
+
if (n.page && (!reg.stacksRoot || !import_node_fs10.default.existsSync(import_node_path11.default.join(reg.stacksRoot, stack.id, n.page)))) {
|
|
6539
|
+
return `stack "${stack.id}" nav "${n.label}" uses page ${n.page}, which does not exist under stacks/${stack.id}/.`;
|
|
6540
|
+
}
|
|
6541
|
+
}
|
|
6542
|
+
return null;
|
|
6543
|
+
}
|
|
6544
|
+
function homeProblem(stack) {
|
|
6545
|
+
if (!stack.home) return null;
|
|
6546
|
+
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.`;
|
|
6547
|
+
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.`;
|
|
6548
|
+
return null;
|
|
6549
|
+
}
|
|
6550
|
+
function stackPageFile(reg, stack, n) {
|
|
6551
|
+
return n.page && reg.stacksRoot ? import_node_path11.default.join(reg.stacksRoot, stack.id, n.page) : null;
|
|
6552
|
+
}
|
|
6553
|
+
function homeRedirect(stack) {
|
|
6554
|
+
if (stack.home) return stack.home === "/" ? null : stack.home;
|
|
6555
|
+
return stack.nav.length && !stack.nav.some((n) => n.path === "/") ? stack.nav[0].path : null;
|
|
6556
|
+
}
|
|
6557
|
+
function renderPages(reg, stack, templates) {
|
|
6558
|
+
const pageTpl = import_node_fs10.default.readFileSync(import_node_path11.default.join(templates, "_pages", "route-page.tsx.tmpl"), "utf8");
|
|
6559
|
+
const files = stack.nav.map((n) => {
|
|
6560
|
+
const block = findBlock(reg, n.block);
|
|
6561
|
+
const comp = block.components.find((c) => c.name === n.component);
|
|
6562
|
+
const custom = stackPageFile(reg, stack, n);
|
|
6563
|
+
const vars2 = {
|
|
6564
|
+
stackId: stack.id,
|
|
6565
|
+
block: block.id,
|
|
6566
|
+
blockDir: `components/g8/${block.dir}`,
|
|
6567
|
+
component: n.component,
|
|
6568
|
+
importPath: componentImportPath(block, comp.file),
|
|
6569
|
+
propsJson: JSON.stringify(n.props ?? {}, null, 2)
|
|
6570
|
+
};
|
|
6571
|
+
return { rel: pageFileFor(n.path), content: render(custom ? import_node_fs10.default.readFileSync(custom, "utf8") : pageTpl, vars2) };
|
|
6572
|
+
});
|
|
6573
|
+
const to = homeRedirect(stack);
|
|
6574
|
+
if (to) {
|
|
6575
|
+
const redirect = import_node_fs10.default.readFileSync(import_node_path11.default.join(templates, "_pages", "redirect-page.tsx.tmpl"), "utf8");
|
|
6576
|
+
files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(to) }) });
|
|
6577
|
+
}
|
|
6578
|
+
return files;
|
|
6579
|
+
}
|
|
6580
|
+
function peerDependenciesFor(blocks, io, stack) {
|
|
6581
|
+
const peers = {};
|
|
6582
|
+
for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) peers[name] ??= range;
|
|
6583
|
+
const usesUi = blocks.some((b) => walk(b.root).some((f) => /\.(tsx?|jsx?)$/.test(f) && /["']@graph8\/sdk\/ui["'/]/.test(import_node_fs10.default.readFileSync(import_node_path11.default.join(b.root, f), "utf8"))));
|
|
6584
|
+
if (usesUi && io.sdkPackageJson && import_node_fs10.default.existsSync(io.sdkPackageJson)) {
|
|
6585
|
+
const sdk = JSON.parse(import_node_fs10.default.readFileSync(io.sdkPackageJson, "utf8"));
|
|
6586
|
+
for (const [name, range] of Object.entries(sdk.peerDependencies ?? {})) peers[name] ??= range;
|
|
6587
|
+
}
|
|
6588
|
+
if (navUsesExtras(stack.nav)) peers["lucide-react"] ??= lucideRange(io);
|
|
6589
|
+
return peers;
|
|
6590
|
+
}
|
|
6591
|
+
function lucideRange(io) {
|
|
6592
|
+
try {
|
|
6593
|
+
const sdk = JSON.parse(import_node_fs10.default.readFileSync(io.sdkPackageJson ?? "", "utf8"));
|
|
6594
|
+
return sdk.peerDependencies?.["lucide-react"] ?? ">=0.400.0";
|
|
6595
|
+
} catch {
|
|
6596
|
+
return ">=0.400.0";
|
|
6597
|
+
}
|
|
6598
|
+
}
|
|
6599
|
+
function mergedPackageJson(rendered, peers) {
|
|
6600
|
+
const pkg = JSON.parse(rendered);
|
|
6601
|
+
for (const [name, range] of Object.entries(peers)) pkg.dependencies[name] ??= range;
|
|
6602
|
+
pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
|
|
6603
|
+
return `${JSON.stringify(pkg, null, 2)}
|
|
6604
|
+
`;
|
|
6605
|
+
}
|
|
6606
|
+
function withAuth(files, auth, templatesRoot) {
|
|
6607
|
+
const patch = (rel, fn) => {
|
|
6608
|
+
const f = files.find((x) => x.rel === rel);
|
|
6609
|
+
if (!f) throw new Error(`--auth ${auth}: the template has no ${rel}`);
|
|
6610
|
+
const next = fn(f.content);
|
|
6611
|
+
if (next === null) throw new Error(`--auth ${auth}: ${rel} does not have the generated shape`);
|
|
6612
|
+
f.content = next;
|
|
6613
|
+
};
|
|
6614
|
+
patch("README.md", (s) => applyAuthToReadme(s, auth));
|
|
6615
|
+
patch(".env.local.example", (s) => applyAuthToEnvExample(s, auth));
|
|
6616
|
+
if (usesSignInScreen(auth)) patch("app/layout.tsx", applyAuthToLayout);
|
|
6617
|
+
return [...files, ...planAuthFiles(auth, templatesRoot)];
|
|
6618
|
+
}
|
|
6619
|
+
function planCreate(reg, stack, appName, pm, io, app, auth) {
|
|
6620
|
+
const templates = import_node_path11.default.join(io.templatesRoot, "next-app");
|
|
6621
|
+
const blocks = resolveWithDeps(reg, stack.blocks);
|
|
6622
|
+
const devCommand = pm === "npm" ? "npm run dev" : `${pm} dev`;
|
|
6623
|
+
const vars2 = {
|
|
6624
|
+
appName,
|
|
6625
|
+
sdkVersion: SDK_VERSION,
|
|
6626
|
+
stackId: stack.id,
|
|
6627
|
+
title: stack.title,
|
|
6628
|
+
titleJson: JSON.stringify(stack.title),
|
|
6629
|
+
...navParts(stack),
|
|
6630
|
+
devCommand,
|
|
6631
|
+
installCommand: `${pm} install`,
|
|
6632
|
+
doctorCommand: app ? "npx @graph8/sdk doctor" : `npx @graph8/sdk doctor --stack ${stack.id}`,
|
|
6633
|
+
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>`)"
|
|
6634
|
+
};
|
|
6635
|
+
const files = [];
|
|
6636
|
+
for (const rel of walk(templates).filter((f) => !f.startsWith("_pages/"))) {
|
|
6637
|
+
let content = render(import_node_fs10.default.readFileSync(import_node_path11.default.join(templates, rel), "utf8"), vars2);
|
|
6638
|
+
if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io, stack));
|
|
6639
|
+
files.push({ rel: outputName(rel), content });
|
|
6640
|
+
}
|
|
6641
|
+
files.push(...renderPages(reg, stack, templates));
|
|
6642
|
+
files.push({ rel: CONFIG_FILE, content: renderBrandConfig({ name: stack.title, ...DEFAULT_FIELDS }) });
|
|
6643
|
+
const ledger = { sdkVersion: SDK_VERSION, target: "components/g8", stack: stack.id, ...app ? { app } : {}, blocks: {} };
|
|
6644
|
+
files.push({ rel: "graph8.json", content: `${JSON.stringify(ledger, null, 2)}
|
|
6645
|
+
` });
|
|
6646
|
+
return auth ? withAuth(files, auth, io.templatesRoot) : files;
|
|
6647
|
+
}
|
|
6648
|
+
function dirProblem(abs, force) {
|
|
6649
|
+
if (!import_node_fs10.default.existsSync(abs)) return null;
|
|
6650
|
+
if (!import_node_fs10.default.statSync(abs).isDirectory()) return `${abs} exists and is not a directory.`;
|
|
6651
|
+
if (force || import_node_fs10.default.readdirSync(abs).length === 0) return null;
|
|
6652
|
+
return `${abs} is not empty. Pick a new directory, or pass --force to write into it (template files are overwritten).`;
|
|
6653
|
+
}
|
|
6654
|
+
function runCreate(given, reg, io, cwd) {
|
|
6655
|
+
let opts = given;
|
|
6656
|
+
if (io.orgBrand && given.orgBrand !== false) {
|
|
6657
|
+
const brand = mergeOrgBrand(given.brand, io.orgBrand, { name: given.title !== void 0 });
|
|
6658
|
+
if (hasBrandInput(brand)) opts = { ...given, brand };
|
|
6659
|
+
}
|
|
6660
|
+
let app;
|
|
6661
|
+
let stack = opts.blocks ? void 0 : reg.stacks.find((s) => s.id === opts.stack);
|
|
6662
|
+
if (opts.blocks) {
|
|
6663
|
+
let composed;
|
|
6664
|
+
try {
|
|
6665
|
+
composed = composeApp(reg, opts.blocks, { title: opts.title, withDepsScreens: opts.depsScreens === "with", noDepsScreens: opts.depsScreens === "none" });
|
|
6666
|
+
} catch (e) {
|
|
6667
|
+
io.err(e.message);
|
|
6668
|
+
return 1;
|
|
6669
|
+
}
|
|
6670
|
+
stack = composed.stack;
|
|
6671
|
+
app = { title: stack.title, blocks: [...new Set(opts.blocks)], screens: composed.screens.map((x) => ({ block: x.block, component: x.component, path: x.path })) };
|
|
6672
|
+
composed.warnings.forEach((w) => io.err(`warning: ${w}`));
|
|
6673
|
+
} else if (!stack) {
|
|
6674
|
+
io.err(`unknown stack "${opts.stack}". Stacks: ${reg.stacks.map((s) => s.id).join(", ") || "none"} (\`graph8-sdk list\`).`);
|
|
6675
|
+
return 1;
|
|
6676
|
+
}
|
|
6677
|
+
if (!stack) return 1;
|
|
6678
|
+
const name = opts.brand?.name?.trim();
|
|
6679
|
+
if (name) {
|
|
6680
|
+
stack = { ...stack, title: name };
|
|
6681
|
+
if (app) app = { ...app, title: name };
|
|
6682
|
+
}
|
|
6683
|
+
const problem = stackProblem(reg, stack);
|
|
6684
|
+
if (problem) return io.err(problem), 1;
|
|
6685
|
+
const abs = import_node_path11.default.resolve(cwd, opts.dir);
|
|
6686
|
+
const dirIssue = dirProblem(abs, opts.force);
|
|
6687
|
+
if (dirIssue) return io.err(dirIssue), 1;
|
|
6688
|
+
const brandIssue = opts.brand ? brandProblem(opts.brand, cwd) : null;
|
|
6689
|
+
if (brandIssue) return io.err(brandIssue), 1;
|
|
6690
|
+
const pm = opts.packageManager ?? packageManagerFromUserAgent(io.userAgent);
|
|
6691
|
+
let files;
|
|
6692
|
+
try {
|
|
6693
|
+
files = planCreate(reg, stack, appNameFor(opts.dir), pm, io, app, opts.auth);
|
|
6694
|
+
} catch (e) {
|
|
6695
|
+
io.err(e.message);
|
|
6696
|
+
return 1;
|
|
6697
|
+
}
|
|
6698
|
+
const blocks = resolveWithDeps(reg, stack.blocks);
|
|
6699
|
+
io.out(`${opts.dryRun ? "Dry run: " : ""}creating "${stack.title}" (${app ? `custom: ${app.blocks.join(", ")}` : stack.id}) in ${opts.dir}`);
|
|
6700
|
+
for (const f of files) io.out(`${opts.dryRun ? "would write" : "write"} ${f.rel}`);
|
|
6701
|
+
if (opts.dryRun) {
|
|
6702
|
+
io.out(`would add blocks: ${blocks.map((b) => b.id).join(", ")}`);
|
|
6703
|
+
if (opts.brand) io.out(`would brand the app: ${Object.entries(opts.brand).map(([k, v]) => `${k} ${v}`).join(", ")}`);
|
|
6704
|
+
if (opts.auth) io.out(`would set up sign-in (--auth ${opts.auth}): ${authSummary(opts.auth)}; authorize: appAuth.authorize in app/api/g8/[...path]/route.ts`);
|
|
6705
|
+
io.out(opts.install ? `would run: ${pm} install` : "would not install dependencies (--no-install)");
|
|
6706
|
+
return 0;
|
|
6707
|
+
}
|
|
6708
|
+
for (const f of files) {
|
|
6709
|
+
const dest = import_node_path11.default.join(abs, f.rel);
|
|
6710
|
+
import_node_fs10.default.mkdirSync(import_node_path11.default.dirname(dest), { recursive: true });
|
|
6711
|
+
import_node_fs10.default.writeFileSync(dest, f.content, "utf8");
|
|
6712
|
+
}
|
|
6713
|
+
return finish(opts, reg, stack, blocks, abs, pm, io, cwd);
|
|
6714
|
+
}
|
|
6715
|
+
function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
|
|
6716
|
+
for (const b of blocks) {
|
|
6717
|
+
const code = runAdd({ spec: b.id, dir: abs, target: "components/g8", dryRun: false, force: opts.force, install: false, wireScreens: false }, reg, io, cwd);
|
|
6718
|
+
if (code !== 0) {
|
|
6719
|
+
io.err(`adding block "${b.id}" failed; the app in ${opts.dir} is incomplete.`);
|
|
6720
|
+
return code;
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
if (opts.auth) {
|
|
6724
|
+
const code = finishAuth(opts.auth, abs, io);
|
|
6725
|
+
if (code !== 0) return code;
|
|
6726
|
+
}
|
|
6727
|
+
if (opts.brand) {
|
|
6728
|
+
io.out("branding the app (graph8-sdk brand)");
|
|
6729
|
+
const code = runBrand({ ...opts.brand, dir: abs }, io, cwd, stack.title);
|
|
6730
|
+
if (code !== 0) {
|
|
6731
|
+
io.err(`branding failed; the app in ${opts.dir} is complete but unbranded. Fix the option and run \`npx @graph8/sdk brand\` in it.`);
|
|
6732
|
+
return code;
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
if (opts.install) {
|
|
6736
|
+
io.out(`installing dependencies: ${pm} install`);
|
|
6737
|
+
const code = io.spawn(pm, ["install"], abs);
|
|
6738
|
+
if (code !== 0) {
|
|
6739
|
+
io.err(`dependency install failed (exit ${code}). Run it yourself: cd ${quoted(opts.dir)} && ${pm} install`);
|
|
6740
|
+
return 1;
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
6743
|
+
const cd = opts.dir === "." ? "" : `cd ${quoted(opts.dir)} && `;
|
|
6744
|
+
io.out(
|
|
6745
|
+
[
|
|
6746
|
+
"",
|
|
6747
|
+
`Done. Next:`,
|
|
6748
|
+
` 1. ${cd}${opts.install ? "" : `${pm} install && `}${pm === "npm" ? "npm run dev" : `${pm} dev`}`,
|
|
6749
|
+
...opts.auth ? authOutro(opts.auth, opts.dir) : [],
|
|
6750
|
+
` 2. open http://localhost:3000 and paste a graph8 API key in the Connect graph8 screen (local preview: only`,
|
|
6751
|
+
` this computer can reach the app's /api/g8 route), or set G8_API_KEY in ${import_node_path11.default.posix.join(opts.dir, ".env.local")}`,
|
|
6752
|
+
` 3. ${cd}npx @graph8/sdk doctor${opts.blocks ? "" : ` --stack ${stack.id}`} (before going live: add --production)`
|
|
6753
|
+
].join("\n")
|
|
6754
|
+
);
|
|
6755
|
+
return 0;
|
|
6756
|
+
}
|
|
6757
|
+
function finishAuth(auth, abs, io) {
|
|
6758
|
+
const rel = "app/api/g8/[...path]/route.ts";
|
|
6759
|
+
const file = import_node_path11.default.join(abs, rel);
|
|
6760
|
+
const next = import_node_fs10.default.existsSync(file) ? applyAuthToRoute(import_node_fs10.default.readFileSync(file, "utf8")) : null;
|
|
6761
|
+
if (next === null) {
|
|
6762
|
+
io.err(`--auth ${auth}: ${rel} does not have the generated shape; set \`authorize: appAuth.authorize\` (import { appAuth } from "@/g8.auth") in it yourself.`);
|
|
6763
|
+
return 1;
|
|
6764
|
+
}
|
|
6765
|
+
import_node_fs10.default.writeFileSync(file, next, "utf8");
|
|
6766
|
+
io.out(`sign-in (--auth ${auth}): ${authSummary(auth)}`);
|
|
6767
|
+
io.out(`update ${rel} (authorize: appAuth.authorize, from g8.auth.ts)`);
|
|
6768
|
+
const written = writeAuthSecrets(abs, auth);
|
|
6769
|
+
if (written.length) io.out(`write .env.local (fresh ${written.join(" and ")} for local development; git-ignored, not printed)`);
|
|
6770
|
+
return 0;
|
|
6771
|
+
}
|
|
6772
|
+
function authOutro(auth, dir) {
|
|
6773
|
+
const env = import_node_path11.default.posix.join(dir, ".env.local");
|
|
6774
|
+
if (auth === "password") return [` sign in with G8_APP_PASSWORD from ${env} (set your own G8_APP_PASSWORD and G8_SESSION_SECRET on your host)`];
|
|
6775
|
+
if (auth === "basic") return [` the browser asks for the G8_BASIC_AUTH user and password in ${env}`];
|
|
6776
|
+
if (auth === "jwt") return [` set your identity provider in ${env} (G8_JWT_ISSUER, G8_JWT_AUDIENCE, G8_JWT_COOKIE, G8_SIGN_IN_URL; see g8.auth.ts)`];
|
|
6777
|
+
if (auth === "session") return [" wire your session library in g8.auth.ts (Clerk, NextAuth, Supabase examples inside); until then it refuses everyone"];
|
|
6778
|
+
return [" WARNING: --auth anonymous: anyone who can reach this app can use its graph8 API key. Use a narrow key with a spend cap."];
|
|
6779
|
+
}
|
|
6780
|
+
|
|
6451
6781
|
// src/cli.ts
|
|
6452
6782
|
var LIST_USAGE = `Usage: npx @graph8/sdk list [--json]
|
|
6453
6783
|
|
|
@@ -6471,7 +6801,7 @@ Run a command with --help for its options.
|
|
|
6471
6801
|
|
|
6472
6802
|
${INIT_AGENT_USAGE}`;
|
|
6473
6803
|
function registryFor(io) {
|
|
6474
|
-
const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ??
|
|
6804
|
+
const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ?? import_node_path12.default.resolve(__dirname, "..", "blocks");
|
|
6475
6805
|
return loadRegistry(root);
|
|
6476
6806
|
}
|
|
6477
6807
|
function defaultSpawn(cmd, args, cwd) {
|
|
@@ -6545,14 +6875,15 @@ function runCreateCommand(rest, io) {
|
|
|
6545
6875
|
if (wantsHelp(rest)) return io.out(CREATE_USAGE), 0;
|
|
6546
6876
|
try {
|
|
6547
6877
|
const opts = parseCreateArgs(rest);
|
|
6548
|
-
const packageRoot =
|
|
6878
|
+
const packageRoot = import_node_path12.default.resolve(__dirname, "..");
|
|
6549
6879
|
const cio = {
|
|
6550
6880
|
out: io.out,
|
|
6551
6881
|
err: io.err,
|
|
6552
6882
|
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
|
|
6883
|
+
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path12.default.join(packageRoot, "templates"),
|
|
6884
|
+
sdkPackageJson: import_node_path12.default.join(packageRoot, "package.json"),
|
|
6885
|
+
userAgent: io.userAgent ?? process.env.npm_config_user_agent,
|
|
6886
|
+
orgBrand: io.orgBrand
|
|
6556
6887
|
};
|
|
6557
6888
|
return runCreate(opts, registryFor(io), cio, io.cwd);
|
|
6558
6889
|
} catch (e) {
|
|
@@ -6563,13 +6894,13 @@ function runAddCommand(rest, io) {
|
|
|
6563
6894
|
if (wantsHelp(rest)) return io.out(ADD_USAGE), 0;
|
|
6564
6895
|
try {
|
|
6565
6896
|
const opts = parseAddArgs(rest);
|
|
6566
|
-
const packageRoot =
|
|
6897
|
+
const packageRoot = import_node_path12.default.resolve(__dirname, "..");
|
|
6567
6898
|
const aio = {
|
|
6568
6899
|
out: io.out,
|
|
6569
6900
|
err: io.err,
|
|
6570
6901
|
spawn: io.spawn ?? defaultSpawn,
|
|
6571
|
-
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ??
|
|
6572
|
-
sdkPackageJson:
|
|
6902
|
+
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path12.default.join(packageRoot, "templates"),
|
|
6903
|
+
sdkPackageJson: import_node_path12.default.join(packageRoot, "package.json")
|
|
6573
6904
|
};
|
|
6574
6905
|
return runAdd(opts, registryFor(io), aio, io.cwd);
|
|
6575
6906
|
} catch (e) {
|
|
@@ -6589,7 +6920,7 @@ function runCli(argv, io) {
|
|
|
6589
6920
|
if (command === "brand") {
|
|
6590
6921
|
if (wantsHelp(rest)) return io.out(BRAND_USAGE), 0;
|
|
6591
6922
|
try {
|
|
6592
|
-
return runBrand(parseBrandArgs(rest), { out: io.out, err: io.err }, io.cwd);
|
|
6923
|
+
return runBrand(parseBrandArgs(rest), { out: io.out, err: io.err }, io.cwd, void 0, io.orgBrand);
|
|
6593
6924
|
} catch (e) {
|
|
6594
6925
|
return fail(io, e.message);
|
|
6595
6926
|
}
|
|
@@ -6613,8 +6944,8 @@ ${USAGE}`);
|
|
|
6613
6944
|
return 1;
|
|
6614
6945
|
}
|
|
6615
6946
|
const read = (rel) => {
|
|
6616
|
-
const full =
|
|
6617
|
-
return
|
|
6947
|
+
const full = import_node_path12.default.join(io.cwd, rel);
|
|
6948
|
+
return import_node_fs11.default.existsSync(full) ? import_node_fs11.default.readFileSync(full, "utf8") : null;
|
|
6618
6949
|
};
|
|
6619
6950
|
let plans;
|
|
6620
6951
|
try {
|
|
@@ -6625,17 +6956,36 @@ ${USAGE}`);
|
|
|
6625
6956
|
}
|
|
6626
6957
|
for (const plan of plans) {
|
|
6627
6958
|
if (!options.dryRun && plan.action !== "unchanged") {
|
|
6628
|
-
const full =
|
|
6629
|
-
|
|
6630
|
-
|
|
6959
|
+
const full = import_node_path12.default.join(io.cwd, plan.path);
|
|
6960
|
+
import_node_fs11.default.mkdirSync(import_node_path12.default.dirname(full), { recursive: true });
|
|
6961
|
+
import_node_fs11.default.writeFileSync(full, plan.content, "utf8");
|
|
6631
6962
|
}
|
|
6632
6963
|
io.out(`${options.dryRun ? "would " : ""}${plan.action === "unchanged" ? "keep" : plan.action} ${plan.path} (${plan.target})`);
|
|
6633
6964
|
}
|
|
6634
6965
|
if (!options.dryRun) io.out("\nDone. The full guide is node_modules/@graph8/sdk/docs/agents.md.");
|
|
6635
6966
|
return 0;
|
|
6636
6967
|
}
|
|
6968
|
+
function brandRoot(command, rest, io) {
|
|
6969
|
+
const at = rest.findIndex((a) => a === "--dir" || a.startsWith("--dir="));
|
|
6970
|
+
const dir = at < 0 ? void 0 : rest[at].includes("=") ? rest[at].slice(6) : rest[at + 1];
|
|
6971
|
+
return command === "brand" && dir ? import_node_path12.default.resolve(io.cwd, dir) : io.cwd;
|
|
6972
|
+
}
|
|
6973
|
+
async function runWithOrgBrand(command, rest, argv, io) {
|
|
6974
|
+
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=")));
|
|
6975
|
+
if (!wants) return runCli(argv, io);
|
|
6976
|
+
const pull = await pullOrgBrand({ env: io.env ?? process.env, fetch: io.fetch }, brandRoot(command, rest, io));
|
|
6977
|
+
io.out(pull.info);
|
|
6978
|
+
const bare = command === "brand" && !rest.includes("--from-org") && !rest.some((a) => a.startsWith("--") && a !== "--dir" && !a.startsWith("--dir="));
|
|
6979
|
+
if (bare && !Object.keys(pull.values).length) return runCli(argv, io);
|
|
6980
|
+
try {
|
|
6981
|
+
return runCli(bare ? [...argv, "--from-org"] : argv, { ...io, orgBrand: pull.values });
|
|
6982
|
+
} finally {
|
|
6983
|
+
pull.cleanup();
|
|
6984
|
+
}
|
|
6985
|
+
}
|
|
6637
6986
|
async function runCliAsync(argv, io) {
|
|
6638
6987
|
const [command, ...rest] = argv;
|
|
6988
|
+
if (command === "create" || command === "brand") return runWithOrgBrand(command, rest, argv, io);
|
|
6639
6989
|
if (command !== "doctor") return runCli(argv, io);
|
|
6640
6990
|
if (wantsHelp(rest)) return io.out(DOCTOR_USAGE), 0;
|
|
6641
6991
|
try {
|