@graph8/sdk 0.373.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 +3 -3
- package/blocks/_foundation/README.md +47 -0
- package/blocks/_foundation/block.json +13 -0
- package/blocks/_foundation/components/auth-gate.tsx +363 -0
- package/blocks/_foundation/server/auth.ts +815 -0
- package/blocks/campaigns/README.md +1 -1
- package/blocks/campaigns/block.json +34 -1
- package/blocks/campaigns/components/campaign-links.tsx +180 -0
- package/blocks/company-intelligence/components/research-run.tsx +6 -2
- package/blocks/crm/README.md +19 -0
- package/blocks/crm/block.json +205 -6
- package/blocks/crm/components/bulk-update.tsx +119 -0
- 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 +47 -6
- 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 +46 -5
- package/blocks/crm/components/owner-assign.tsx +132 -0
- 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/unlock-info-button.tsx +82 -0
- package/blocks/crm/components/verify-email-button.tsx +93 -0
- package/blocks/crm/hooks/use-contact-tags.ts +29 -0
- package/blocks/crm/lib/record-actions.ts +102 -0
- package/blocks/deals/README.md +1 -0
- package/blocks/deals/block.json +17 -0
- package/blocks/deals/components/create-deal-button.tsx +109 -0
- package/blocks/deals/components/deal-detail.tsx +5 -3
- package/blocks/dialer/components/call-log-screen.tsx +6 -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 +22 -4
- package/blocks/enrichment/block.json +145 -5
- 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 +7 -7
- 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 +34 -3
- 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/lib/enrichment-types.ts +12 -0
- package/blocks/enrichment/lib/pipeline-types.ts +234 -0
- package/blocks/forms/README.md +2 -0
- package/blocks/forms/components/forms-screen.tsx +6 -3
- package/blocks/inbox/README.md +40 -4
- package/blocks/inbox/block.json +130 -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/tag-manager.tsx +136 -0
- package/blocks/inbox/components/thread-actions.tsx +133 -0
- package/blocks/inbox/components/thread-view.tsx +2 -0
- package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
- package/blocks/inbox/lib/inbox-manage.ts +227 -0
- package/blocks/newsletter/components/newsletter-screen.tsx +3 -1
- package/blocks/pages/block.json +10 -1
- package/blocks/pages/hooks/use-landing-page-paths.ts +60 -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 +25 -1
- package/blocks/quotes/block.json +123 -6
- package/blocks/quotes/components/manual-products.tsx +144 -0
- package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
- package/blocks/quotes/components/quote-documents.tsx +94 -0
- package/blocks/quotes/components/quote-evidence.tsx +95 -0
- package/blocks/quotes/components/quote-list.tsx +23 -5
- 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 +16 -3
- package/blocks/quotes/components/quotes-screen.tsx +15 -5
- package/blocks/quotes/lib/quote-admin.ts +264 -0
- package/blocks/sequencer/README.md +55 -0
- package/blocks/sequencer/block.json +172 -5
- package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
- package/blocks/sequencer/components/sequence-builder.tsx +13 -3
- package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
- package/blocks/sequencer/components/sequence-detail.tsx +6 -0
- package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
- package/blocks/sequencer/components/sequence-enrollments.tsx +2 -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-templates.tsx +160 -0
- package/blocks/sequencer/components/sequences-screen.tsx +36 -6
- package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
- package/blocks/sequencer/components/step-editor.tsx +10 -1
- 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 +4 -0
- 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/visitors/components/traffic-chart.tsx +3 -2
- 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/workbench/README.md +54 -2
- package/blocks/workbench/block.json +234 -5
- 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 +70 -7
- 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 +50 -17
- package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
- 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/dist/cli.js +257 -51
- 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.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.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/create.d.ts +5 -2
- package/dist/types/version.d.ts +2 -2
- package/docs/api/forms.md +1 -1
- package/package.json +1 -1
- package/stacks/6sense/pages/contacts.tsx.tmpl +2 -0
- package/stacks/6sense.json +20 -0
- package/stacks/apollo/pages/calls.tsx.tmpl +9 -9
- package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
- package/stacks/apollo/pages/contacts.tsx.tmpl +6 -0
- package/stacks/apollo/pages/inbox-contact.tsx.tmpl +3 -3
- package/stacks/apollo/pages/lists.tsx.tmpl +14 -2
- package/stacks/apollo/pages/sequences.tsx.tmpl +8 -2
- package/stacks/apollo.json +33 -2
- package/stacks/clay/pages/contacts.tsx.tmpl +2 -0
- package/stacks/clay/pages/lists.tsx.tmpl +14 -2
- package/stacks/gong/pages/calls.tsx.tmpl +9 -9
- package/stacks/gong/pages/contacts.tsx.tmpl +2 -0
- package/stacks/hootsuite/pages/contacts.tsx.tmpl +2 -0
- package/stacks/hootsuite.json +10 -0
- package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
- package/stacks/hubspot/pages/companies.tsx.tmpl +2 -0
- package/stacks/hubspot/pages/contacts.tsx.tmpl +6 -0
- package/stacks/hubspot/pages/deals.tsx.tmpl +4 -0
- package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
- package/stacks/hubspot/pages/inbox.tsx.tmpl +2 -2
- package/stacks/hubspot/pages/sequences.tsx.tmpl +8 -2
- package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
- package/stacks/hubspot.json +45 -2
- package/stacks/intercom/pages/inbox-contact.tsx.tmpl +3 -3
- package/stacks/intercom.json +10 -0
- package/stacks/mailchimp/pages/contacts.tsx.tmpl +2 -0
- package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
- package/stacks/mailchimp.json +2 -1
- package/stacks/outreach/pages/calls.tsx.tmpl +9 -9
- package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
- package/stacks/outreach/pages/contacts.tsx.tmpl +2 -0
- package/stacks/outreach/pages/inbox-contact.tsx.tmpl +3 -3
- package/stacks/outreach/pages/sequences.tsx.tmpl +8 -2
- package/stacks/outreach.json +13 -2
- package/stacks/pipedrive/pages/contacts.tsx.tmpl +2 -0
- package/stacks/pipedrive/pages/inbox.tsx.tmpl +2 -2
- package/stacks/pipedrive.json +10 -0
- package/stacks/salesforce/pages/companies.tsx.tmpl +2 -0
- package/stacks/salesforce/pages/contacts.tsx.tmpl +6 -0
- package/stacks/salesforce/pages/deals.tsx.tmpl +4 -0
- package/stacks/salesforce.json +10 -0
- package/stacks/zoominfo/pages/contacts.tsx.tmpl +4 -0
- package/stacks/zoominfo/pages/lists.tsx.tmpl +14 -2
- package/stacks/zoominfo.json +20 -0
- 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/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
|
|
@@ -5618,7 +5618,7 @@ function warnIfTargetOrgIgnored(apiKey, headers) {
|
|
|
5618
5618
|
);
|
|
5619
5619
|
}
|
|
5620
5620
|
}
|
|
5621
|
-
async function request(baseUrl,
|
|
5621
|
+
async function request(baseUrl, path13, apiKey, opts = {}) {
|
|
5622
5622
|
const {
|
|
5623
5623
|
method = "GET",
|
|
5624
5624
|
body,
|
|
@@ -5631,7 +5631,7 @@ async function request(baseUrl, path12, apiKey, opts = {}) {
|
|
|
5631
5631
|
fetchImpl = fetch,
|
|
5632
5632
|
sleepImpl = defaultSleep
|
|
5633
5633
|
} = opts;
|
|
5634
|
-
const url = baseUrl.replace(/\/+$/, "") +
|
|
5634
|
+
const url = baseUrl.replace(/\/+$/, "") + path13 + buildQuery(query);
|
|
5635
5635
|
const finalHeaders = {
|
|
5636
5636
|
"Content-Type": "application/json",
|
|
5637
5637
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -5992,7 +5992,7 @@ async function runDoctor(opts, reg, io, cwd) {
|
|
|
5992
5992
|
await checkKey(call, blocks, key, line);
|
|
5993
5993
|
await checkSubscription(call, key, line);
|
|
5994
5994
|
}
|
|
5995
|
-
checkRoute(root, blocks, opts.production === true, io.out, line);
|
|
5995
|
+
checkRoute(root, blocks, opts.production === true, io.out, line, io.env);
|
|
5996
5996
|
if (opts.stack) printFlows(opts, reg, io.out, line);
|
|
5997
5997
|
else if (!opts.blocks.length) printAppScreens(root, reg, io.out);
|
|
5998
5998
|
printChecklist(blocks, io.out);
|
|
@@ -6079,7 +6079,7 @@ var ROUTE_FILES = ["app/api/g8/[...path]/route.ts", "src/app/api/g8/[...path]/ro
|
|
|
6079
6079
|
function findRoute(root) {
|
|
6080
6080
|
return ROUTE_FILES.find((f) => import_node_fs7.default.existsSync(import_node_path8.default.join(root, f))) ?? null;
|
|
6081
6081
|
}
|
|
6082
|
-
function checkRoute(root, blocks, production, out, line) {
|
|
6082
|
+
function checkRoute(root, blocks, production, out, line, env = {}) {
|
|
6083
6083
|
const rel = findRoute(root);
|
|
6084
6084
|
if (!rel) {
|
|
6085
6085
|
if (production) line(false, `no proxy route found (${ROUTE_FILES[0]}): run doctor from the app's root or pass --dir`);
|
|
@@ -6094,6 +6094,7 @@ Proxy route (${rel})${production ? ", production gate" : ""}:`);
|
|
|
6094
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)"
|
|
6095
6095
|
);
|
|
6096
6096
|
} else line(true, "`authorize` is set");
|
|
6097
|
+
checkAuthConfig(root, problem, out, env);
|
|
6097
6098
|
if (facts.dangerouslyAllowUnauthenticated) problem("dangerouslyAllowUnauthenticated is on: anyone who can reach the route can use the key");
|
|
6098
6099
|
if (facts.dangerouslyAllowAllPaths) problem("dangerouslyAllowAllPaths is on: every /api/v1 area is reachable from the browser");
|
|
6099
6100
|
const need = derivePrefixes(blocks);
|
|
@@ -6109,6 +6110,21 @@ Proxy route (${rel})${production ? ", production gate" : ""}:`);
|
|
|
6109
6110
|
if (extra.length && blocks.length) out(`warn allowedPrefixes allows areas no installed block declares: ${extra.join(", ")}`);
|
|
6110
6111
|
}
|
|
6111
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
|
+
}
|
|
6112
6128
|
function fixPrefixes(root, blocks, io) {
|
|
6113
6129
|
const rel = findRoute(root);
|
|
6114
6130
|
if (!rel) {
|
|
@@ -6225,8 +6241,142 @@ async function pullOrgBrand(io, root) {
|
|
|
6225
6241
|
}
|
|
6226
6242
|
|
|
6227
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");
|
|
6228
6249
|
var import_node_fs9 = __toESM(require("fs"));
|
|
6229
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
|
|
6230
6380
|
var CREATE_USAGE = `Usage: npx @graph8/sdk create <stack> <dir> [options]
|
|
6231
6381
|
|
|
6232
6382
|
Scaffold a Next.js (App Router, TypeScript, Tailwind CSS v4) app for a stack and
|
|
@@ -6236,6 +6386,14 @@ install the stack's blocks into it. Run \`graph8-sdk list\` to see the stacks.
|
|
|
6236
6386
|
--dry-run print what would be written, write and install nothing
|
|
6237
6387
|
--force allow a non-empty <dir> (template files are overwritten, and graph8.json
|
|
6238
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).
|
|
6239
6397
|
Then: \`npm run dev\` in <dir>, open http://localhost:3000 and paste a graph8 API key (local preview), and run
|
|
6240
6398
|
\`npx @graph8/sdk doctor --stack <stack>\` (add --production before going live).
|
|
6241
6399
|
|
|
@@ -6278,6 +6436,12 @@ ${CREATE_USAGE}`);
|
|
|
6278
6436
|
|
|
6279
6437
|
${CREATE_USAGE}`);
|
|
6280
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;
|
|
6281
6445
|
} else if (flag === "--title") {
|
|
6282
6446
|
const v = inline ?? argv[++i];
|
|
6283
6447
|
if (v === void 0 || v.startsWith("--")) throw new Error(`--title needs a value
|
|
@@ -6333,23 +6497,23 @@ function packageManagerFromUserAgent(ua) {
|
|
|
6333
6497
|
return name ?? "npm";
|
|
6334
6498
|
}
|
|
6335
6499
|
function appNameFor(dir) {
|
|
6336
|
-
const base =
|
|
6500
|
+
const base = import_node_path11.default.basename(import_node_path11.default.resolve(dir)).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|-+$/g, "");
|
|
6337
6501
|
return base || "g8-app";
|
|
6338
6502
|
}
|
|
6339
6503
|
var quoted = (p) => /^[\w@%+=:,./-]+$/.test(p) ? p : JSON.stringify(p);
|
|
6340
6504
|
function walk(dir, base = dir) {
|
|
6341
6505
|
const out = [];
|
|
6342
|
-
for (const e of
|
|
6343
|
-
const full =
|
|
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);
|
|
6344
6508
|
if (e.isDirectory()) out.push(...walk(full, base));
|
|
6345
|
-
else out.push(posix(
|
|
6509
|
+
else out.push(posix(import_node_path11.default.relative(base, full)));
|
|
6346
6510
|
}
|
|
6347
6511
|
return out;
|
|
6348
6512
|
}
|
|
6349
6513
|
function outputName(rel) {
|
|
6350
6514
|
const noExt = rel.replace(/\.tmpl$/, "");
|
|
6351
|
-
const dir =
|
|
6352
|
-
const base =
|
|
6515
|
+
const dir = import_node_path11.default.posix.dirname(noExt);
|
|
6516
|
+
const base = import_node_path11.default.posix.basename(noExt).replace(/^dot-/, ".");
|
|
6353
6517
|
return dir === "." ? base : `${dir}/${base}`;
|
|
6354
6518
|
}
|
|
6355
6519
|
function stackProblem(reg, stack) {
|
|
@@ -6371,7 +6535,7 @@ function stackProblem(reg, stack) {
|
|
|
6371
6535
|
if (/[\[\]]/.test(n.path)) return `stack "${stack.id}" nav "${n.label}" uses ${n.path}: dynamic segments cannot be a sidebar link.`;
|
|
6372
6536
|
if (seen.has(n.path)) return `stack "${stack.id}" has two nav entries at ${n.path}.`;
|
|
6373
6537
|
seen.add(n.path);
|
|
6374
|
-
if (n.page && (!reg.stacksRoot || !
|
|
6538
|
+
if (n.page && (!reg.stacksRoot || !import_node_fs10.default.existsSync(import_node_path11.default.join(reg.stacksRoot, stack.id, n.page)))) {
|
|
6375
6539
|
return `stack "${stack.id}" nav "${n.label}" uses page ${n.page}, which does not exist under stacks/${stack.id}/.`;
|
|
6376
6540
|
}
|
|
6377
6541
|
}
|
|
@@ -6384,14 +6548,14 @@ function homeProblem(stack) {
|
|
|
6384
6548
|
return null;
|
|
6385
6549
|
}
|
|
6386
6550
|
function stackPageFile(reg, stack, n) {
|
|
6387
|
-
return n.page && reg.stacksRoot ?
|
|
6551
|
+
return n.page && reg.stacksRoot ? import_node_path11.default.join(reg.stacksRoot, stack.id, n.page) : null;
|
|
6388
6552
|
}
|
|
6389
6553
|
function homeRedirect(stack) {
|
|
6390
6554
|
if (stack.home) return stack.home === "/" ? null : stack.home;
|
|
6391
6555
|
return stack.nav.length && !stack.nav.some((n) => n.path === "/") ? stack.nav[0].path : null;
|
|
6392
6556
|
}
|
|
6393
6557
|
function renderPages(reg, stack, templates) {
|
|
6394
|
-
const pageTpl =
|
|
6558
|
+
const pageTpl = import_node_fs10.default.readFileSync(import_node_path11.default.join(templates, "_pages", "route-page.tsx.tmpl"), "utf8");
|
|
6395
6559
|
const files = stack.nav.map((n) => {
|
|
6396
6560
|
const block = findBlock(reg, n.block);
|
|
6397
6561
|
const comp = block.components.find((c) => c.name === n.component);
|
|
@@ -6404,11 +6568,11 @@ function renderPages(reg, stack, templates) {
|
|
|
6404
6568
|
importPath: componentImportPath(block, comp.file),
|
|
6405
6569
|
propsJson: JSON.stringify(n.props ?? {}, null, 2)
|
|
6406
6570
|
};
|
|
6407
|
-
return { rel: pageFileFor(n.path), content: render(custom ?
|
|
6571
|
+
return { rel: pageFileFor(n.path), content: render(custom ? import_node_fs10.default.readFileSync(custom, "utf8") : pageTpl, vars2) };
|
|
6408
6572
|
});
|
|
6409
6573
|
const to = homeRedirect(stack);
|
|
6410
6574
|
if (to) {
|
|
6411
|
-
const redirect =
|
|
6575
|
+
const redirect = import_node_fs10.default.readFileSync(import_node_path11.default.join(templates, "_pages", "redirect-page.tsx.tmpl"), "utf8");
|
|
6412
6576
|
files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(to) }) });
|
|
6413
6577
|
}
|
|
6414
6578
|
return files;
|
|
@@ -6416,9 +6580,9 @@ function renderPages(reg, stack, templates) {
|
|
|
6416
6580
|
function peerDependenciesFor(blocks, io, stack) {
|
|
6417
6581
|
const peers = {};
|
|
6418
6582
|
for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) peers[name] ??= range;
|
|
6419
|
-
const usesUi = blocks.some((b) => walk(b.root).some((f) => /\.(tsx?|jsx?)$/.test(f) && /["']@graph8\/sdk\/ui["'/]/.test(
|
|
6420
|
-
if (usesUi && io.sdkPackageJson &&
|
|
6421
|
-
const sdk = JSON.parse(
|
|
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"));
|
|
6422
6586
|
for (const [name, range] of Object.entries(sdk.peerDependencies ?? {})) peers[name] ??= range;
|
|
6423
6587
|
}
|
|
6424
6588
|
if (navUsesExtras(stack.nav)) peers["lucide-react"] ??= lucideRange(io);
|
|
@@ -6426,7 +6590,7 @@ function peerDependenciesFor(blocks, io, stack) {
|
|
|
6426
6590
|
}
|
|
6427
6591
|
function lucideRange(io) {
|
|
6428
6592
|
try {
|
|
6429
|
-
const sdk = JSON.parse(
|
|
6593
|
+
const sdk = JSON.parse(import_node_fs10.default.readFileSync(io.sdkPackageJson ?? "", "utf8"));
|
|
6430
6594
|
return sdk.peerDependencies?.["lucide-react"] ?? ">=0.400.0";
|
|
6431
6595
|
} catch {
|
|
6432
6596
|
return ">=0.400.0";
|
|
@@ -6439,8 +6603,21 @@ function mergedPackageJson(rendered, peers) {
|
|
|
6439
6603
|
return `${JSON.stringify(pkg, null, 2)}
|
|
6440
6604
|
`;
|
|
6441
6605
|
}
|
|
6442
|
-
function
|
|
6443
|
-
const
|
|
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");
|
|
6444
6621
|
const blocks = resolveWithDeps(reg, stack.blocks);
|
|
6445
6622
|
const devCommand = pm === "npm" ? "npm run dev" : `${pm} dev`;
|
|
6446
6623
|
const vars2 = {
|
|
@@ -6457,7 +6634,7 @@ function planCreate(reg, stack, appName, pm, io, app) {
|
|
|
6457
6634
|
};
|
|
6458
6635
|
const files = [];
|
|
6459
6636
|
for (const rel of walk(templates).filter((f) => !f.startsWith("_pages/"))) {
|
|
6460
|
-
let content = render(
|
|
6637
|
+
let content = render(import_node_fs10.default.readFileSync(import_node_path11.default.join(templates, rel), "utf8"), vars2);
|
|
6461
6638
|
if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io, stack));
|
|
6462
6639
|
files.push({ rel: outputName(rel), content });
|
|
6463
6640
|
}
|
|
@@ -6466,12 +6643,12 @@ function planCreate(reg, stack, appName, pm, io, app) {
|
|
|
6466
6643
|
const ledger = { sdkVersion: SDK_VERSION, target: "components/g8", stack: stack.id, ...app ? { app } : {}, blocks: {} };
|
|
6467
6644
|
files.push({ rel: "graph8.json", content: `${JSON.stringify(ledger, null, 2)}
|
|
6468
6645
|
` });
|
|
6469
|
-
return files;
|
|
6646
|
+
return auth ? withAuth(files, auth, io.templatesRoot) : files;
|
|
6470
6647
|
}
|
|
6471
6648
|
function dirProblem(abs, force) {
|
|
6472
|
-
if (!
|
|
6473
|
-
if (!
|
|
6474
|
-
if (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;
|
|
6475
6652
|
return `${abs} is not empty. Pick a new directory, or pass --force to write into it (template files are overwritten).`;
|
|
6476
6653
|
}
|
|
6477
6654
|
function runCreate(given, reg, io, cwd) {
|
|
@@ -6505,7 +6682,7 @@ function runCreate(given, reg, io, cwd) {
|
|
|
6505
6682
|
}
|
|
6506
6683
|
const problem = stackProblem(reg, stack);
|
|
6507
6684
|
if (problem) return io.err(problem), 1;
|
|
6508
|
-
const abs =
|
|
6685
|
+
const abs = import_node_path11.default.resolve(cwd, opts.dir);
|
|
6509
6686
|
const dirIssue = dirProblem(abs, opts.force);
|
|
6510
6687
|
if (dirIssue) return io.err(dirIssue), 1;
|
|
6511
6688
|
const brandIssue = opts.brand ? brandProblem(opts.brand, cwd) : null;
|
|
@@ -6513,7 +6690,7 @@ function runCreate(given, reg, io, cwd) {
|
|
|
6513
6690
|
const pm = opts.packageManager ?? packageManagerFromUserAgent(io.userAgent);
|
|
6514
6691
|
let files;
|
|
6515
6692
|
try {
|
|
6516
|
-
files = planCreate(reg, stack, appNameFor(opts.dir), pm, io, app);
|
|
6693
|
+
files = planCreate(reg, stack, appNameFor(opts.dir), pm, io, app, opts.auth);
|
|
6517
6694
|
} catch (e) {
|
|
6518
6695
|
io.err(e.message);
|
|
6519
6696
|
return 1;
|
|
@@ -6524,13 +6701,14 @@ function runCreate(given, reg, io, cwd) {
|
|
|
6524
6701
|
if (opts.dryRun) {
|
|
6525
6702
|
io.out(`would add blocks: ${blocks.map((b) => b.id).join(", ")}`);
|
|
6526
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`);
|
|
6527
6705
|
io.out(opts.install ? `would run: ${pm} install` : "would not install dependencies (--no-install)");
|
|
6528
6706
|
return 0;
|
|
6529
6707
|
}
|
|
6530
6708
|
for (const f of files) {
|
|
6531
|
-
const dest =
|
|
6532
|
-
|
|
6533
|
-
|
|
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");
|
|
6534
6712
|
}
|
|
6535
6713
|
return finish(opts, reg, stack, blocks, abs, pm, io, cwd);
|
|
6536
6714
|
}
|
|
@@ -6542,6 +6720,10 @@ function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
|
|
|
6542
6720
|
return code;
|
|
6543
6721
|
}
|
|
6544
6722
|
}
|
|
6723
|
+
if (opts.auth) {
|
|
6724
|
+
const code = finishAuth(opts.auth, abs, io);
|
|
6725
|
+
if (code !== 0) return code;
|
|
6726
|
+
}
|
|
6545
6727
|
if (opts.brand) {
|
|
6546
6728
|
io.out("branding the app (graph8-sdk brand)");
|
|
6547
6729
|
const code = runBrand({ ...opts.brand, dir: abs }, io, cwd, stack.title);
|
|
@@ -6564,13 +6746,37 @@ function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
|
|
|
6564
6746
|
"",
|
|
6565
6747
|
`Done. Next:`,
|
|
6566
6748
|
` 1. ${cd}${opts.install ? "" : `${pm} install && `}${pm === "npm" ? "npm run dev" : `${pm} dev`}`,
|
|
6749
|
+
...opts.auth ? authOutro(opts.auth, opts.dir) : [],
|
|
6567
6750
|
` 2. open http://localhost:3000 and paste a graph8 API key in the Connect graph8 screen (local preview: only`,
|
|
6568
|
-
` this computer can reach the app's /api/g8 route), or set G8_API_KEY in ${
|
|
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")}`,
|
|
6569
6752
|
` 3. ${cd}npx @graph8/sdk doctor${opts.blocks ? "" : ` --stack ${stack.id}`} (before going live: add --production)`
|
|
6570
6753
|
].join("\n")
|
|
6571
6754
|
);
|
|
6572
6755
|
return 0;
|
|
6573
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
|
+
}
|
|
6574
6780
|
|
|
6575
6781
|
// src/cli.ts
|
|
6576
6782
|
var LIST_USAGE = `Usage: npx @graph8/sdk list [--json]
|
|
@@ -6595,7 +6801,7 @@ Run a command with --help for its options.
|
|
|
6595
6801
|
|
|
6596
6802
|
${INIT_AGENT_USAGE}`;
|
|
6597
6803
|
function registryFor(io) {
|
|
6598
|
-
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");
|
|
6599
6805
|
return loadRegistry(root);
|
|
6600
6806
|
}
|
|
6601
6807
|
function defaultSpawn(cmd, args, cwd) {
|
|
@@ -6669,13 +6875,13 @@ function runCreateCommand(rest, io) {
|
|
|
6669
6875
|
if (wantsHelp(rest)) return io.out(CREATE_USAGE), 0;
|
|
6670
6876
|
try {
|
|
6671
6877
|
const opts = parseCreateArgs(rest);
|
|
6672
|
-
const packageRoot =
|
|
6878
|
+
const packageRoot = import_node_path12.default.resolve(__dirname, "..");
|
|
6673
6879
|
const cio = {
|
|
6674
6880
|
out: io.out,
|
|
6675
6881
|
err: io.err,
|
|
6676
6882
|
spawn: io.spawn ?? defaultSpawn,
|
|
6677
|
-
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ??
|
|
6678
|
-
sdkPackageJson:
|
|
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"),
|
|
6679
6885
|
userAgent: io.userAgent ?? process.env.npm_config_user_agent,
|
|
6680
6886
|
orgBrand: io.orgBrand
|
|
6681
6887
|
};
|
|
@@ -6688,13 +6894,13 @@ function runAddCommand(rest, io) {
|
|
|
6688
6894
|
if (wantsHelp(rest)) return io.out(ADD_USAGE), 0;
|
|
6689
6895
|
try {
|
|
6690
6896
|
const opts = parseAddArgs(rest);
|
|
6691
|
-
const packageRoot =
|
|
6897
|
+
const packageRoot = import_node_path12.default.resolve(__dirname, "..");
|
|
6692
6898
|
const aio = {
|
|
6693
6899
|
out: io.out,
|
|
6694
6900
|
err: io.err,
|
|
6695
6901
|
spawn: io.spawn ?? defaultSpawn,
|
|
6696
|
-
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ??
|
|
6697
|
-
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")
|
|
6698
6904
|
};
|
|
6699
6905
|
return runAdd(opts, registryFor(io), aio, io.cwd);
|
|
6700
6906
|
} catch (e) {
|
|
@@ -6738,8 +6944,8 @@ ${USAGE}`);
|
|
|
6738
6944
|
return 1;
|
|
6739
6945
|
}
|
|
6740
6946
|
const read = (rel) => {
|
|
6741
|
-
const full =
|
|
6742
|
-
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;
|
|
6743
6949
|
};
|
|
6744
6950
|
let plans;
|
|
6745
6951
|
try {
|
|
@@ -6750,9 +6956,9 @@ ${USAGE}`);
|
|
|
6750
6956
|
}
|
|
6751
6957
|
for (const plan of plans) {
|
|
6752
6958
|
if (!options.dryRun && plan.action !== "unchanged") {
|
|
6753
|
-
const full =
|
|
6754
|
-
|
|
6755
|
-
|
|
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");
|
|
6756
6962
|
}
|
|
6757
6963
|
io.out(`${options.dryRun ? "would " : ""}${plan.action === "unchanged" ? "keep" : plan.action} ${plan.path} (${plan.target})`);
|
|
6758
6964
|
}
|
|
@@ -6762,7 +6968,7 @@ ${USAGE}`);
|
|
|
6762
6968
|
function brandRoot(command, rest, io) {
|
|
6763
6969
|
const at = rest.findIndex((a) => a === "--dir" || a.startsWith("--dir="));
|
|
6764
6970
|
const dir = at < 0 ? void 0 : rest[at].includes("=") ? rest[at].slice(6) : rest[at + 1];
|
|
6765
|
-
return command === "brand" && dir ?
|
|
6971
|
+
return command === "brand" && dir ? import_node_path12.default.resolve(io.cwd, dir) : io.cwd;
|
|
6766
6972
|
}
|
|
6767
6973
|
async function runWithOrgBrand(command, rest, argv, io) {
|
|
6768
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=")));
|