@graph8/sdk 0.358.0 → 0.362.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/blocks/_foundation/README.md +108 -0
- package/blocks/_foundation/block.json +160 -12
- package/blocks/_foundation/components/api-error.tsx +62 -18
- package/blocks/_foundation/components/empty-state.tsx +39 -7
- package/blocks/_foundation/components/screen/bar-list.tsx +71 -0
- package/blocks/_foundation/components/screen/detail-panel.tsx +114 -0
- package/blocks/_foundation/components/screen/entity-avatar.tsx +56 -0
- package/blocks/_foundation/components/screen/form-sheet.tsx +73 -0
- package/blocks/_foundation/components/screen/index.ts +21 -0
- package/blocks/_foundation/components/screen/key-value-list.tsx +65 -0
- package/blocks/_foundation/components/screen/screen-layout.tsx +124 -0
- package/blocks/_foundation/components/screen/section-card.tsx +47 -0
- package/blocks/_foundation/components/screen/sparkline.tsx +77 -0
- package/blocks/_foundation/components/screen/split-view.tsx +125 -0
- package/blocks/_foundation/components/screen/stat-strip.tsx +121 -0
- package/blocks/_foundation/components/screen/status-pill.tsx +104 -0
- package/blocks/_foundation/components/screen/time-series-chart.tsx +114 -0
- package/blocks/_foundation/components/screen/toolbar.tsx +144 -0
- package/blocks/_foundation/hooks/use-url-state.ts +74 -0
- package/blocks/appointments/README.md +21 -1
- package/blocks/appointments/block.json +39 -2
- package/blocks/appointments/components/event-type-related.tsx +75 -0
- package/blocks/appointments/components/meeting-list.tsx +8 -4
- package/blocks/appointments/components/meetings-screen.tsx +154 -0
- package/blocks/appointments/components/scheduling-screen.tsx +121 -0
- package/blocks/appointments/hooks/use-event-types.ts +17 -0
- package/blocks/block.types.ts +9 -0
- package/blocks/deals/README.md +34 -3
- package/blocks/deals/block.json +39 -4
- package/blocks/deals/components/deal-detail.tsx +4 -2
- package/blocks/deals/components/deal-related.tsx +87 -0
- package/blocks/deals/components/deals-screen.tsx +293 -0
- package/blocks/deals/components/pipeline-board.tsx +4 -2
- package/blocks/deals/hooks/use-default-owner.ts +60 -0
- package/blocks/deals/lib/deals-types.ts +14 -0
- package/blocks/forms/README.md +12 -0
- package/blocks/forms/block.json +9 -1
- package/blocks/forms/components/forms-screen.tsx +350 -0
- package/blocks/forms/components/submissions-table.tsx +13 -5
- package/blocks/lifecycle/README.md +16 -0
- package/blocks/lifecycle/block.json +23 -1
- package/blocks/lifecycle/components/account-related.tsx +66 -0
- package/blocks/lifecycle/components/lifecycle-screen.tsx +147 -0
- package/blocks/newsletter/README.md +13 -0
- package/blocks/newsletter/block.json +9 -1
- package/blocks/newsletter/components/newsletter-screen.tsx +402 -0
- package/blocks/newsletter/components/subscriber-table.tsx +12 -6
- package/blocks/nurture/README.md +12 -0
- package/blocks/nurture/block.json +9 -1
- package/blocks/nurture/components/nurture-screen.tsx +279 -0
- package/blocks/pages/README.md +12 -0
- package/blocks/pages/block.json +9 -1
- package/blocks/pages/components/content-library.tsx +37 -7
- package/blocks/pages/components/page-list.tsx +4 -2
- package/blocks/pages/components/pages-screen.tsx +154 -0
- package/blocks/quotes/README.md +17 -0
- package/blocks/quotes/block.json +26 -2
- package/blocks/quotes/components/quote-list.tsx +7 -3
- package/blocks/quotes/components/quote-related.tsx +120 -0
- package/blocks/quotes/components/quotes-screen.tsx +169 -0
- package/dist/cli.js +197 -37
- 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 +93 -7
- package/dist/index.d.ts +93 -7
- 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 +90 -4
- package/dist/react.d.ts +90 -4
- 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/create.d.ts +15 -1
- package/dist/types/blocks/doctor.d.ts +2 -1
- package/dist/types/blocks/flows.d.ts +20 -0
- package/dist/types/blocks/registry.d.ts +15 -0
- package/dist/types/core.d.ts +3 -2
- package/dist/types/react.d.ts +2 -0
- package/dist/types/studio.d.ts +48 -0
- package/dist/types/version.d.ts +2 -2
- package/docs/api/adCampaigns.d.ts +4 -0
- package/docs/api/adCampaigns.md +4 -0
- package/docs/api/gtmCampaigns.d.ts +4 -0
- package/docs/api/gtmCampaigns.md +2 -0
- package/docs/api/gtmContext.d.ts +5 -1
- package/docs/api/gtmContext.md +15 -7
- package/docs/api/integrations.d.ts +29 -4
- package/docs/api/integrations.md +14 -0
- package/docs/sdk-clients.md +1 -1
- package/package.json +1 -1
- package/stacks/stack.schema.json +6 -1
- package/styles/styles.css +1 -1
- package/templates/next-app/README.md.tmpl +16 -0
- package/templates/next-app/app/globals.css.tmpl +105 -0
- package/templates/next-app/app/layout.tsx.tmpl +11 -2
- package/templates/next-app/components/app-shell.tsx.tmpl +238 -23
- package/templates/next-app/components/shell/connection-status.tsx.tmpl +109 -0
- package/templates/next-app/components/shell/nav-model.ts.tmpl +65 -0
- package/templates/next-app/components/shell/quick-jump.tsx.tmpl +131 -0
- package/templates/next-app/components/shell/theme-toggle.tsx.tmpl +40 -0
- package/templates/next-app/lib/nav.ts.tmpl +1 -1
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_fs6 = __toESM(require("fs"));
|
|
40
|
+
var import_node_path6 = __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, path7, next) => {
|
|
123
|
+
const current = read(path7);
|
|
124
|
+
plans.push({ target, path: path7, 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_fs2 = __toESM(require("fs"));
|
|
|
158
158
|
var import_node_path2 = __toESM(require("path"));
|
|
159
159
|
|
|
160
160
|
// src/version.ts
|
|
161
|
-
var SDK_VERSION = "0.
|
|
161
|
+
var SDK_VERSION = "0.362.0";
|
|
162
162
|
var SDK_CLIENT_ID = `js-sdk/${SDK_VERSION}`;
|
|
163
163
|
|
|
164
164
|
// src/blocks/registry.ts
|
|
@@ -220,11 +220,12 @@ function loadRegistry(blocksRoot) {
|
|
|
220
220
|
summary: typeof raw.summary === "string" ? raw.summary : "",
|
|
221
221
|
status: raw.status === "planned" ? "planned" : "available",
|
|
222
222
|
blocks: arr(raw.blocks),
|
|
223
|
-
nav: arr(raw.nav)
|
|
223
|
+
nav: arr(raw.nav),
|
|
224
|
+
...typeof raw.home === "string" ? { home: raw.home } : {}
|
|
224
225
|
});
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
|
-
return { blocks, stacks };
|
|
228
|
+
return { blocks, stacks, stacksRoot: stacksDir };
|
|
228
229
|
}
|
|
229
230
|
function findBlock(reg, id) {
|
|
230
231
|
return reg.blocks.find((b) => b.id === id);
|
|
@@ -262,8 +263,19 @@ function searchStacks(reg, text) {
|
|
|
262
263
|
if (!needle) return [];
|
|
263
264
|
return reg.stacks.filter((s) => [s.id, s.title, s.summary].some((x) => x.toLowerCase().includes(needle)));
|
|
264
265
|
}
|
|
266
|
+
function screenNames(b) {
|
|
267
|
+
return b.components.map((c) => c.name).filter((n) => n.endsWith("Screen"));
|
|
268
|
+
}
|
|
265
269
|
function blockSummary(b) {
|
|
266
|
-
|
|
270
|
+
const screens = screenNames(b);
|
|
271
|
+
return {
|
|
272
|
+
id: b.id,
|
|
273
|
+
title: b.title,
|
|
274
|
+
summary: b.summary,
|
|
275
|
+
dependsOn: b.dependsOn,
|
|
276
|
+
components: b.components.map((c) => c.name),
|
|
277
|
+
...screens.length ? { screens } : {}
|
|
278
|
+
};
|
|
267
279
|
}
|
|
268
280
|
function stackStatus(reg, s) {
|
|
269
281
|
const missing = s.blocks.filter((id) => !findBlock(reg, id));
|
|
@@ -280,7 +292,8 @@ function catalogSection(reg) {
|
|
|
280
292
|
const lines = ["### graph8 blocks", "", "Ready-made, copy-in UI + hooks per graph8 feature. Pick from this list instead of reading every README:", ""];
|
|
281
293
|
for (const b of reg.blocks) {
|
|
282
294
|
const deps = b.dependsOn.length ? ` (needs ${b.dependsOn.join(", ")})` : "";
|
|
283
|
-
|
|
295
|
+
const screens = screenNames(b);
|
|
296
|
+
lines.push(`- block \`${b.id}\`: ${b.summary}${deps}${screens.length ? ` Screens (start here): ${screens.join(", ")}.` : ""}`);
|
|
284
297
|
}
|
|
285
298
|
for (const s of reg.stacks) {
|
|
286
299
|
const { missing } = stackStatus(reg, s);
|
|
@@ -715,6 +728,8 @@ function stackProblem(reg, stack) {
|
|
|
715
728
|
].join("\n");
|
|
716
729
|
}
|
|
717
730
|
const seen = /* @__PURE__ */ new Set();
|
|
731
|
+
const homeIssue = homeProblem(stack);
|
|
732
|
+
if (homeIssue) return homeIssue;
|
|
718
733
|
for (const n of stack.nav) {
|
|
719
734
|
const block = findBlock(reg, n.block);
|
|
720
735
|
if (!stack.blocks.includes(n.block)) return `stack "${stack.id}" nav "${n.label}" uses block "${n.block}" which the stack does not include.`;
|
|
@@ -722,31 +737,77 @@ function stackProblem(reg, stack) {
|
|
|
722
737
|
if (/[\[\]]/.test(n.path)) return `stack "${stack.id}" nav "${n.label}" uses ${n.path}: dynamic segments cannot be a sidebar link.`;
|
|
723
738
|
if (seen.has(n.path)) return `stack "${stack.id}" has two nav entries at ${n.path}.`;
|
|
724
739
|
seen.add(n.path);
|
|
740
|
+
if (n.page && (!reg.stacksRoot || !import_node_fs3.default.existsSync(import_node_path3.default.join(reg.stacksRoot, stack.id, n.page)))) {
|
|
741
|
+
return `stack "${stack.id}" nav "${n.label}" uses page ${n.page}, which does not exist under stacks/${stack.id}/.`;
|
|
742
|
+
}
|
|
725
743
|
}
|
|
726
744
|
return null;
|
|
727
745
|
}
|
|
746
|
+
function homeProblem(stack) {
|
|
747
|
+
if (!stack.home) return null;
|
|
748
|
+
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.`;
|
|
749
|
+
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.`;
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
function stackPageFile(reg, stack, n) {
|
|
753
|
+
return n.page && reg.stacksRoot ? import_node_path3.default.join(reg.stacksRoot, stack.id, n.page) : null;
|
|
754
|
+
}
|
|
755
|
+
function homeRedirect(stack) {
|
|
756
|
+
if (stack.home) return stack.home === "/" ? null : stack.home;
|
|
757
|
+
return stack.nav.length && !stack.nav.some((n) => n.path === "/") ? stack.nav[0].path : null;
|
|
758
|
+
}
|
|
728
759
|
function renderPages(reg, stack, templates) {
|
|
729
760
|
const pageTpl = import_node_fs3.default.readFileSync(import_node_path3.default.join(templates, "_pages", "route-page.tsx.tmpl"), "utf8");
|
|
730
761
|
const files = stack.nav.map((n) => {
|
|
731
762
|
const block = findBlock(reg, n.block);
|
|
732
763
|
const comp = block.components.find((c) => c.name === n.component);
|
|
733
|
-
const
|
|
764
|
+
const custom = stackPageFile(reg, stack, n);
|
|
765
|
+
const vars = {
|
|
734
766
|
stackId: stack.id,
|
|
735
767
|
block: block.id,
|
|
736
768
|
blockDir: `components/g8/${block.dir}`,
|
|
737
769
|
component: n.component,
|
|
738
770
|
importPath: componentImportPath(block, comp.file),
|
|
739
771
|
propsJson: JSON.stringify(n.props ?? {}, null, 2)
|
|
740
|
-
}
|
|
741
|
-
return { rel: pageFileFor(n.path), content };
|
|
772
|
+
};
|
|
773
|
+
return { rel: pageFileFor(n.path), content: render(custom ? import_node_fs3.default.readFileSync(custom, "utf8") : pageTpl, vars) };
|
|
742
774
|
});
|
|
743
|
-
|
|
775
|
+
const to = homeRedirect(stack);
|
|
776
|
+
if (to) {
|
|
744
777
|
const redirect = import_node_fs3.default.readFileSync(import_node_path3.default.join(templates, "_pages", "redirect-page.tsx.tmpl"), "utf8");
|
|
745
|
-
files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(
|
|
778
|
+
files.push({ rel: "app/page.tsx", content: render(redirect, { firstPathJson: JSON.stringify(to) }) });
|
|
746
779
|
}
|
|
747
780
|
return files;
|
|
748
781
|
}
|
|
749
|
-
function
|
|
782
|
+
function navUsesExtras(stack) {
|
|
783
|
+
return stack.nav.some((n) => n.icon || n.group || n.description);
|
|
784
|
+
}
|
|
785
|
+
function navParts(stack) {
|
|
786
|
+
if (!navUsesExtras(stack)) {
|
|
787
|
+
return { navImports: "", navType: "{ label: string; path: string }[]", navJson: JSON.stringify(stack.nav.map((n) => ({ label: n.label, path: n.path })), null, 2) };
|
|
788
|
+
}
|
|
789
|
+
const icons = [...new Set(stack.nav.flatMap((n) => n.icon ? [n.icon] : []))].sort();
|
|
790
|
+
const named = [...icons, "type LucideIcon"].join(", ");
|
|
791
|
+
const items = stack.nav.map((n) => ({
|
|
792
|
+
label: n.label,
|
|
793
|
+
path: n.path,
|
|
794
|
+
...n.icon ? { icon: `__ICON__${n.icon}` } : {},
|
|
795
|
+
...n.group ? { group: n.group } : {},
|
|
796
|
+
...n.description ? { description: n.description } : {}
|
|
797
|
+
}));
|
|
798
|
+
return {
|
|
799
|
+
navImports: [
|
|
800
|
+
`import { ${named} } from "lucide-react";`,
|
|
801
|
+
"",
|
|
802
|
+
"export type NavItem = { label: string; path: string; icon?: LucideIcon; group?: string; description?: string };",
|
|
803
|
+
"",
|
|
804
|
+
""
|
|
805
|
+
].join("\n"),
|
|
806
|
+
navType: "NavItem[]",
|
|
807
|
+
navJson: JSON.stringify(items, null, 2).replace(/"__ICON__(\w+)"/g, "$1")
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
function peerDependenciesFor(blocks, io, stack) {
|
|
750
811
|
const peers = {};
|
|
751
812
|
for (const b of blocks) for (const [name, range] of Object.entries(b.peerDependencies)) peers[name] ??= range;
|
|
752
813
|
const usesUi = blocks.some((b) => walk(b.root).some((f) => /\.(tsx?|jsx?)$/.test(f) && /["']@graph8\/sdk\/ui["'/]/.test(import_node_fs3.default.readFileSync(import_node_path3.default.join(b.root, f), "utf8"))));
|
|
@@ -754,8 +815,17 @@ function peerDependenciesFor(blocks, io) {
|
|
|
754
815
|
const sdk = JSON.parse(import_node_fs3.default.readFileSync(io.sdkPackageJson, "utf8"));
|
|
755
816
|
for (const [name, range] of Object.entries(sdk.peerDependencies ?? {})) peers[name] ??= range;
|
|
756
817
|
}
|
|
818
|
+
if (navUsesExtras(stack)) peers["lucide-react"] ??= lucideRange(io);
|
|
757
819
|
return peers;
|
|
758
820
|
}
|
|
821
|
+
function lucideRange(io) {
|
|
822
|
+
try {
|
|
823
|
+
const sdk = JSON.parse(import_node_fs3.default.readFileSync(io.sdkPackageJson ?? "", "utf8"));
|
|
824
|
+
return sdk.peerDependencies?.["lucide-react"] ?? ">=0.400.0";
|
|
825
|
+
} catch {
|
|
826
|
+
return ">=0.400.0";
|
|
827
|
+
}
|
|
828
|
+
}
|
|
759
829
|
function mergedPackageJson(rendered, peers) {
|
|
760
830
|
const pkg = JSON.parse(rendered);
|
|
761
831
|
for (const [name, range] of Object.entries(peers)) pkg.dependencies[name] ??= range;
|
|
@@ -773,14 +843,14 @@ function planCreate(reg, stack, appName, pm, io) {
|
|
|
773
843
|
stackId: stack.id,
|
|
774
844
|
title: stack.title,
|
|
775
845
|
titleJson: JSON.stringify(stack.title),
|
|
776
|
-
|
|
846
|
+
...navParts(stack),
|
|
777
847
|
devCommand,
|
|
778
848
|
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>`)"
|
|
779
849
|
};
|
|
780
850
|
const files = [];
|
|
781
851
|
for (const rel of walk(templates).filter((f) => !f.startsWith("_pages/"))) {
|
|
782
852
|
let content = render(import_node_fs3.default.readFileSync(import_node_path3.default.join(templates, rel), "utf8"), vars);
|
|
783
|
-
if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io));
|
|
853
|
+
if (rel === "package.json.tmpl") content = mergedPackageJson(content, peerDependenciesFor(blocks, io, stack));
|
|
784
854
|
files.push({ rel: outputName(rel), content });
|
|
785
855
|
}
|
|
786
856
|
files.push(...renderPages(reg, stack, templates));
|
|
@@ -859,8 +929,8 @@ function finish(opts, reg, stack, blocks, abs, pm, io, cwd) {
|
|
|
859
929
|
}
|
|
860
930
|
|
|
861
931
|
// src/blocks/doctor.ts
|
|
862
|
-
var
|
|
863
|
-
var
|
|
932
|
+
var import_node_fs5 = __toESM(require("fs"));
|
|
933
|
+
var import_node_path5 = __toESM(require("path"));
|
|
864
934
|
|
|
865
935
|
// src/generated/billable.ts
|
|
866
936
|
var BILLABLE_ROUTES = {
|
|
@@ -1680,7 +1750,7 @@ function warnIfTargetOrgIgnored(apiKey, headers) {
|
|
|
1680
1750
|
);
|
|
1681
1751
|
}
|
|
1682
1752
|
}
|
|
1683
|
-
async function request(baseUrl,
|
|
1753
|
+
async function request(baseUrl, path7, apiKey, opts = {}) {
|
|
1684
1754
|
const {
|
|
1685
1755
|
method = "GET",
|
|
1686
1756
|
body,
|
|
@@ -1693,7 +1763,7 @@ async function request(baseUrl, path6, apiKey, opts = {}) {
|
|
|
1693
1763
|
fetchImpl = fetch,
|
|
1694
1764
|
sleepImpl = defaultSleep
|
|
1695
1765
|
} = opts;
|
|
1696
|
-
const url = baseUrl.replace(/\/+$/, "") +
|
|
1766
|
+
const url = baseUrl.replace(/\/+$/, "") + path7 + buildQuery(query);
|
|
1697
1767
|
const finalHeaders = {
|
|
1698
1768
|
"Content-Type": "application/json",
|
|
1699
1769
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -5173,14 +5243,89 @@ function createApiClient(apiKey, apiUrl) {
|
|
|
5173
5243
|
return client;
|
|
5174
5244
|
}
|
|
5175
5245
|
|
|
5246
|
+
// src/blocks/flows.ts
|
|
5247
|
+
var import_node_fs4 = __toESM(require("fs"));
|
|
5248
|
+
var import_node_path4 = __toESM(require("path"));
|
|
5249
|
+
var IMPORT_RE2 = /(?:^|\n)\s*(?:import|export)\s[^'"`;]*?from\s*["']([^"']+)["']|(?:^|\n)\s*import\s*["']([^"']+)["']|import\(\s*["']([^"']+)["']\s*\)/g;
|
|
5250
|
+
var EXTENSIONS = ["", ".tsx", ".ts", "/index.tsx", "/index.ts"];
|
|
5251
|
+
function specifiers(source) {
|
|
5252
|
+
return [...source.matchAll(IMPORT_RE2)].map((m) => m[1] ?? m[2] ?? m[3]);
|
|
5253
|
+
}
|
|
5254
|
+
function resolveFile(base) {
|
|
5255
|
+
for (const ext of EXTENSIONS) {
|
|
5256
|
+
const candidate = base + ext;
|
|
5257
|
+
if (import_node_fs4.default.existsSync(candidate) && import_node_fs4.default.statSync(candidate).isFile()) return candidate;
|
|
5258
|
+
}
|
|
5259
|
+
return null;
|
|
5260
|
+
}
|
|
5261
|
+
function resolveImport(spec, from, reg) {
|
|
5262
|
+
if (spec.startsWith(".")) return resolveFile(import_node_path4.default.resolve(import_node_path4.default.dirname(from), spec));
|
|
5263
|
+
const g8 = /^@\/g8\/([^/]+)\/(.+)$/.exec(spec);
|
|
5264
|
+
if (!g8) return null;
|
|
5265
|
+
const owner = findBlock(reg, g8[1]);
|
|
5266
|
+
if (owner) return resolveFile(import_node_path4.default.join(owner.root, "components", g8[2])) ?? resolveFile(import_node_path4.default.join(owner.root, g8[2]));
|
|
5267
|
+
const foundation = findBlock(reg, "foundation");
|
|
5268
|
+
return foundation ? resolveFile(import_node_path4.default.join(foundation.root, g8[1], g8[2])) : null;
|
|
5269
|
+
}
|
|
5270
|
+
function resolveInstalled(spec, reg) {
|
|
5271
|
+
const m = /^@\/components\/g8\/([^/]+)\/(.+)$/.exec(spec);
|
|
5272
|
+
const block = m && reg.blocks.find((b) => b.dir === m[1]);
|
|
5273
|
+
if (!m || !block) return null;
|
|
5274
|
+
const files = [...block.components, ...block.hooks, ...block.lib].map((x) => x.file);
|
|
5275
|
+
const hit = files.find((f) => (installedRel(f) ?? f).replace(/\.(tsx|ts|jsx|js)$/, "") === m[2]);
|
|
5276
|
+
return hit ? import_node_path4.default.join(block.root, hit) : null;
|
|
5277
|
+
}
|
|
5278
|
+
function seeds(reg, stack) {
|
|
5279
|
+
const out = [];
|
|
5280
|
+
for (const n of stack.nav) {
|
|
5281
|
+
const comp = findBlock(reg, n.block)?.components.find((c) => c.name === n.component);
|
|
5282
|
+
const block = findBlock(reg, n.block);
|
|
5283
|
+
if (!block || !comp) continue;
|
|
5284
|
+
const pageFile = n.page && reg.stacksRoot ? import_node_path4.default.join(reg.stacksRoot, stack.id, n.page) : null;
|
|
5285
|
+
if (!pageFile || !import_node_fs4.default.existsSync(pageFile)) {
|
|
5286
|
+
out.push(import_node_path4.default.join(block.root, comp.file));
|
|
5287
|
+
continue;
|
|
5288
|
+
}
|
|
5289
|
+
const source = import_node_fs4.default.readFileSync(pageFile, "utf8");
|
|
5290
|
+
if (source.includes("{{importPath}}")) out.push(import_node_path4.default.join(block.root, comp.file));
|
|
5291
|
+
for (const spec of specifiers(source)) {
|
|
5292
|
+
const hit = resolveInstalled(spec, reg);
|
|
5293
|
+
if (hit) out.push(hit);
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5296
|
+
return out;
|
|
5297
|
+
}
|
|
5298
|
+
function reachedFiles(reg, stack) {
|
|
5299
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5300
|
+
const queue = seeds(reg, stack);
|
|
5301
|
+
while (queue.length) {
|
|
5302
|
+
const file = queue.pop();
|
|
5303
|
+
if (seen.has(file)) continue;
|
|
5304
|
+
seen.add(file);
|
|
5305
|
+
if (!/\.(tsx?|jsx?)$/.test(file)) continue;
|
|
5306
|
+
for (const spec of specifiers(import_node_fs4.default.readFileSync(file, "utf8"))) {
|
|
5307
|
+
const next = resolveImport(spec, file, reg);
|
|
5308
|
+
if (next && !seen.has(next)) queue.push(next);
|
|
5309
|
+
}
|
|
5310
|
+
}
|
|
5311
|
+
return seen;
|
|
5312
|
+
}
|
|
5313
|
+
function unreachedComponents(reg, stack) {
|
|
5314
|
+
const reached = reachedFiles(reg, stack);
|
|
5315
|
+
const blocks = resolveWithDeps(reg, stack.blocks).filter((b) => b.id !== "foundation" && stack.blocks.includes(b.id));
|
|
5316
|
+
return blocks.flatMap((b) => b.components.filter((c) => !reached.has(import_node_path4.default.join(b.root, c.file))).map((c) => ({ block: b.id, component: c.name })));
|
|
5317
|
+
}
|
|
5318
|
+
|
|
5176
5319
|
// src/blocks/doctor.ts
|
|
5177
|
-
var DOCTOR_USAGE = `Usage: npx @graph8/sdk doctor [--stack <id>] [--blocks a,b] [--dir <path>]
|
|
5320
|
+
var DOCTOR_USAGE = `Usage: npx @graph8/sdk doctor [--stack <id>] [--blocks a,b] [--dir <path>] [--strict]
|
|
5178
5321
|
|
|
5179
5322
|
Check that this project is ready for graph8 blocks:
|
|
5180
5323
|
- G8_API_KEY (env or .env.local) is valid
|
|
5181
5324
|
- the key's scopes cover the blocks you chose
|
|
5182
5325
|
- the organization's subscription is active
|
|
5183
5326
|
- a checklist of in-app setup each block needs
|
|
5327
|
+
With --stack it also lists "Flows": block components that no page of the stack reaches
|
|
5328
|
+
(a warning; --strict makes them fail the run).
|
|
5184
5329
|
With no --stack/--blocks, the blocks recorded in graph8.json are checked.
|
|
5185
5330
|
Optional G8_API_URL points at a non-default API. Exit code 0 when all checks
|
|
5186
5331
|
pass, 1 otherwise.`;
|
|
@@ -5200,6 +5345,7 @@ ${DOCTOR_USAGE}`);
|
|
|
5200
5345
|
if (flag === "--stack") o.stack = value();
|
|
5201
5346
|
else if (flag === "--blocks") o.blocks.push(...value().split(",").map((s) => s.trim()).filter(Boolean));
|
|
5202
5347
|
else if (flag === "--dir") o.dir = value();
|
|
5348
|
+
else if (arg === "--strict") o.strict = true;
|
|
5203
5349
|
else throw new Error(`unknown option ${arg}
|
|
5204
5350
|
|
|
5205
5351
|
${DOCTOR_USAGE}`);
|
|
@@ -5218,9 +5364,9 @@ function parseEnvFile(text) {
|
|
|
5218
5364
|
function readSetting(name, root, env) {
|
|
5219
5365
|
if (env[name]) return env[name];
|
|
5220
5366
|
for (const f of [".env.local"]) {
|
|
5221
|
-
const file =
|
|
5222
|
-
if (
|
|
5223
|
-
const v = parseEnvFile(
|
|
5367
|
+
const file = import_node_path5.default.join(root, f);
|
|
5368
|
+
if (import_node_fs5.default.existsSync(file)) {
|
|
5369
|
+
const v = parseEnvFile(import_node_fs5.default.readFileSync(file, "utf8"))[name];
|
|
5224
5370
|
if (v) return v;
|
|
5225
5371
|
}
|
|
5226
5372
|
}
|
|
@@ -5241,7 +5387,7 @@ function selectBlocks(opts, reg, root) {
|
|
|
5241
5387
|
}
|
|
5242
5388
|
if (!ids.length) {
|
|
5243
5389
|
try {
|
|
5244
|
-
const ledger = JSON.parse(
|
|
5390
|
+
const ledger = JSON.parse(import_node_fs5.default.readFileSync(import_node_path5.default.join(root, "graph8.json"), "utf8"));
|
|
5245
5391
|
ids = Object.keys(ledger.blocks ?? {});
|
|
5246
5392
|
} catch {
|
|
5247
5393
|
ids = [];
|
|
@@ -5255,7 +5401,7 @@ function errorText(e, key) {
|
|
|
5255
5401
|
return `${status ? `HTTP ${status}: ` : ""}${msg}`.split(key).join("[redacted]");
|
|
5256
5402
|
}
|
|
5257
5403
|
async function runDoctor(opts, reg, io, cwd) {
|
|
5258
|
-
const root =
|
|
5404
|
+
const root = import_node_path5.default.resolve(cwd, opts.dir);
|
|
5259
5405
|
let blocks;
|
|
5260
5406
|
try {
|
|
5261
5407
|
blocks = selectBlocks(opts, reg, root);
|
|
@@ -5281,6 +5427,7 @@ async function runDoctor(opts, reg, io, cwd) {
|
|
|
5281
5427
|
await checkKey(call, blocks, key, line);
|
|
5282
5428
|
await checkSubscription(call, key, line);
|
|
5283
5429
|
}
|
|
5430
|
+
if (opts.stack) printFlows(opts, reg, io.out, line);
|
|
5284
5431
|
printChecklist(blocks, io.out);
|
|
5285
5432
|
io.out(failed ? "\nSome checks failed." : "\nAll checks passed.");
|
|
5286
5433
|
return failed ? 1 : 0;
|
|
@@ -5318,6 +5465,18 @@ async function checkSubscription(call, key, line) {
|
|
|
5318
5465
|
else line(false, `could not read subscription status (${errorText(e, key)})`);
|
|
5319
5466
|
}
|
|
5320
5467
|
}
|
|
5468
|
+
function printFlows(opts, reg, out, line) {
|
|
5469
|
+
const stack = reg.stacks.find((s) => s.id === opts.stack);
|
|
5470
|
+
if (!stack) return;
|
|
5471
|
+
const missing = unreachedComponents(reg, stack);
|
|
5472
|
+
out("\nFlows (block components a page of this stack reaches):");
|
|
5473
|
+
if (!missing.length) return line(true, "every component of the stack's blocks is reachable from a page");
|
|
5474
|
+
for (const m of missing) {
|
|
5475
|
+
const text = `(${m.block}) ${m.component} is not reached by any page of "${stack.id}": wire it into a page or a Screen slot`;
|
|
5476
|
+
if (opts.strict) line(false, text);
|
|
5477
|
+
else out(`warn ${text}`);
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5321
5480
|
function printChecklist(blocks, out) {
|
|
5322
5481
|
const items = blocks.flatMap((b) => b.prerequisites.filter((p) => p.where === "app").map((p) => ({ block: b.id, p })));
|
|
5323
5482
|
if (!items.length) return;
|
|
@@ -5349,7 +5508,7 @@ Run a command with --help for its options.
|
|
|
5349
5508
|
|
|
5350
5509
|
${INIT_AGENT_USAGE}`;
|
|
5351
5510
|
function registryFor(io) {
|
|
5352
|
-
const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ??
|
|
5511
|
+
const root = io.blocksRoot ?? process.env.G8_BLOCKS_DIR ?? import_node_path6.default.resolve(__dirname, "..", "blocks");
|
|
5353
5512
|
return loadRegistry(root);
|
|
5354
5513
|
}
|
|
5355
5514
|
function defaultSpawn(cmd, args, cwd) {
|
|
@@ -5362,6 +5521,7 @@ function printCatalog(blocks, stacks, io, empty) {
|
|
|
5362
5521
|
if (blocks.length) io.out("Blocks");
|
|
5363
5522
|
for (const b of blocks) {
|
|
5364
5523
|
io.out(` ${b.id} ${b.summary}`);
|
|
5524
|
+
if (b.screens?.length) io.out(` screens (start here): ${b.screens.join(", ")}`);
|
|
5365
5525
|
if (b.dependsOn.length) io.out(` depends on: ${b.dependsOn.join(", ")}`);
|
|
5366
5526
|
}
|
|
5367
5527
|
if (stacks.length) io.out(`${blocks.length ? "\n" : ""}Stacks`);
|
|
@@ -5413,13 +5573,13 @@ function runCreateCommand(rest, io) {
|
|
|
5413
5573
|
if (wantsHelp(rest)) return io.out(CREATE_USAGE), 0;
|
|
5414
5574
|
try {
|
|
5415
5575
|
const opts = parseCreateArgs(rest);
|
|
5416
|
-
const packageRoot =
|
|
5576
|
+
const packageRoot = import_node_path6.default.resolve(__dirname, "..");
|
|
5417
5577
|
const cio = {
|
|
5418
5578
|
out: io.out,
|
|
5419
5579
|
err: io.err,
|
|
5420
5580
|
spawn: io.spawn ?? defaultSpawn,
|
|
5421
|
-
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ??
|
|
5422
|
-
sdkPackageJson:
|
|
5581
|
+
templatesRoot: io.templatesRoot ?? process.env.G8_TEMPLATES_DIR ?? import_node_path6.default.join(packageRoot, "templates"),
|
|
5582
|
+
sdkPackageJson: import_node_path6.default.join(packageRoot, "package.json"),
|
|
5423
5583
|
userAgent: io.userAgent ?? process.env.npm_config_user_agent
|
|
5424
5584
|
};
|
|
5425
5585
|
return runCreate(opts, registryFor(io), cio, io.cwd);
|
|
@@ -5465,8 +5625,8 @@ ${USAGE}`);
|
|
|
5465
5625
|
return 1;
|
|
5466
5626
|
}
|
|
5467
5627
|
const read = (rel) => {
|
|
5468
|
-
const full =
|
|
5469
|
-
return
|
|
5628
|
+
const full = import_node_path6.default.join(io.cwd, rel);
|
|
5629
|
+
return import_node_fs6.default.existsSync(full) ? import_node_fs6.default.readFileSync(full, "utf8") : null;
|
|
5470
5630
|
};
|
|
5471
5631
|
let plans;
|
|
5472
5632
|
try {
|
|
@@ -5477,9 +5637,9 @@ ${USAGE}`);
|
|
|
5477
5637
|
}
|
|
5478
5638
|
for (const plan of plans) {
|
|
5479
5639
|
if (!options.dryRun && plan.action !== "unchanged") {
|
|
5480
|
-
const full =
|
|
5481
|
-
|
|
5482
|
-
|
|
5640
|
+
const full = import_node_path6.default.join(io.cwd, plan.path);
|
|
5641
|
+
import_node_fs6.default.mkdirSync(import_node_path6.default.dirname(full), { recursive: true });
|
|
5642
|
+
import_node_fs6.default.writeFileSync(full, plan.content, "utf8");
|
|
5483
5643
|
}
|
|
5484
5644
|
io.out(`${options.dryRun ? "would " : ""}${plan.action === "unchanged" ? "keep" : plan.action} ${plan.path} (${plan.target})`);
|
|
5485
5645
|
}
|