@salesforce/graphiti 10.10.2

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.
Files changed (282) hide show
  1. package/AGENT_GUIDE.md +424 -0
  2. package/CHANGELOG.md +448 -0
  3. package/LICENSE.txt +82 -0
  4. package/README.md +204 -0
  5. package/TASK.md +249 -0
  6. package/dist/cli.d.ts +7 -0
  7. package/dist/cli.js +683 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/args.d.ts +13 -0
  10. package/dist/commands/args.js +207 -0
  11. package/dist/commands/args.js.map +1 -0
  12. package/dist/commands/build.d.ts +11 -0
  13. package/dist/commands/build.js +209 -0
  14. package/dist/commands/build.js.map +1 -0
  15. package/dist/commands/connect.d.ts +8 -0
  16. package/dist/commands/connect.js +55 -0
  17. package/dist/commands/connect.js.map +1 -0
  18. package/dist/commands/describe.d.ts +6 -0
  19. package/dist/commands/describe.js +229 -0
  20. package/dist/commands/describe.js.map +1 -0
  21. package/dist/commands/meta.d.ts +9 -0
  22. package/dist/commands/meta.js +140 -0
  23. package/dist/commands/meta.js.map +1 -0
  24. package/dist/commands/navigate.d.ts +14 -0
  25. package/dist/commands/navigate.js +105 -0
  26. package/dist/commands/navigate.js.map +1 -0
  27. package/dist/commands/query-helpers.d.ts +80 -0
  28. package/dist/commands/query-helpers.js +865 -0
  29. package/dist/commands/query-helpers.js.map +1 -0
  30. package/dist/commands/query.d.ts +26 -0
  31. package/dist/commands/query.js +901 -0
  32. package/dist/commands/query.js.map +1 -0
  33. package/dist/commands/review.d.ts +18 -0
  34. package/dist/commands/review.js +533 -0
  35. package/dist/commands/review.js.map +1 -0
  36. package/dist/commands/session-mgmt.d.ts +25 -0
  37. package/dist/commands/session-mgmt.js +206 -0
  38. package/dist/commands/session-mgmt.js.map +1 -0
  39. package/dist/commands/type.d.ts +6 -0
  40. package/dist/commands/type.js +342 -0
  41. package/dist/commands/type.js.map +1 -0
  42. package/dist/commands/validate-input.d.ts +6 -0
  43. package/dist/commands/validate-input.js +32 -0
  44. package/dist/commands/validate-input.js.map +1 -0
  45. package/dist/intent/build-aggregate.d.ts +33 -0
  46. package/dist/intent/build-aggregate.js +134 -0
  47. package/dist/intent/build-aggregate.js.map +1 -0
  48. package/dist/intent/build-create.d.ts +14 -0
  49. package/dist/intent/build-create.js +16 -0
  50. package/dist/intent/build-create.js.map +1 -0
  51. package/dist/intent/build-delete.d.ts +30 -0
  52. package/dist/intent/build-delete.js +53 -0
  53. package/dist/intent/build-delete.js.map +1 -0
  54. package/dist/intent/build-detail.d.ts +32 -0
  55. package/dist/intent/build-detail.js +80 -0
  56. package/dist/intent/build-detail.js.map +1 -0
  57. package/dist/intent/build-discover.d.ts +30 -0
  58. package/dist/intent/build-discover.js +149 -0
  59. package/dist/intent/build-discover.js.map +1 -0
  60. package/dist/intent/build-list.d.ts +28 -0
  61. package/dist/intent/build-list.js +75 -0
  62. package/dist/intent/build-list.js.map +1 -0
  63. package/dist/intent/build-mutation.d.ts +23 -0
  64. package/dist/intent/build-mutation.js +54 -0
  65. package/dist/intent/build-mutation.js.map +1 -0
  66. package/dist/intent/build-output.d.ts +27 -0
  67. package/dist/intent/build-output.js +60 -0
  68. package/dist/intent/build-output.js.map +1 -0
  69. package/dist/intent/build-raw.d.ts +23 -0
  70. package/dist/intent/build-raw.js +54 -0
  71. package/dist/intent/build-raw.js.map +1 -0
  72. package/dist/intent/build-update.d.ts +14 -0
  73. package/dist/intent/build-update.js +16 -0
  74. package/dist/intent/build-update.js.map +1 -0
  75. package/dist/intent/get-schema-with-priming.d.ts +26 -0
  76. package/dist/intent/get-schema-with-priming.js +32 -0
  77. package/dist/intent/get-schema-with-priming.js.map +1 -0
  78. package/dist/intent/select-child-relationship.d.ts +19 -0
  79. package/dist/intent/select-child-relationship.js +38 -0
  80. package/dist/intent/select-child-relationship.js.map +1 -0
  81. package/dist/intent/types.d.ts +159 -0
  82. package/dist/intent/types.js +21 -0
  83. package/dist/intent/types.js.map +1 -0
  84. package/dist/lib/apply-command.d.ts +15 -0
  85. package/dist/lib/apply-command.js +238 -0
  86. package/dist/lib/apply-command.js.map +1 -0
  87. package/dist/lib/auth.d.ts +38 -0
  88. package/dist/lib/auth.js +113 -0
  89. package/dist/lib/auth.js.map +1 -0
  90. package/dist/lib/codegen.d.ts +32 -0
  91. package/dist/lib/codegen.js +700 -0
  92. package/dist/lib/codegen.js.map +1 -0
  93. package/dist/lib/command-registry.d.ts +59 -0
  94. package/dist/lib/command-registry.js +366 -0
  95. package/dist/lib/command-registry.js.map +1 -0
  96. package/dist/lib/formatter.d.ts +76 -0
  97. package/dist/lib/formatter.js +419 -0
  98. package/dist/lib/formatter.js.map +1 -0
  99. package/dist/lib/fs-utils.d.ts +23 -0
  100. package/dist/lib/fs-utils.js +46 -0
  101. package/dist/lib/fs-utils.js.map +1 -0
  102. package/dist/lib/graphql-name.d.ts +27 -0
  103. package/dist/lib/graphql-name.js +32 -0
  104. package/dist/lib/graphql-name.js.map +1 -0
  105. package/dist/lib/interactive.d.ts +6 -0
  106. package/dist/lib/interactive.js +562 -0
  107. package/dist/lib/interactive.js.map +1 -0
  108. package/dist/lib/introspect.d.ts +40 -0
  109. package/dist/lib/introspect.js +280 -0
  110. package/dist/lib/introspect.js.map +1 -0
  111. package/dist/lib/object-info.d.ts +79 -0
  112. package/dist/lib/object-info.js +313 -0
  113. package/dist/lib/object-info.js.map +1 -0
  114. package/dist/lib/path-selection.d.ts +50 -0
  115. package/dist/lib/path-selection.js +146 -0
  116. package/dist/lib/path-selection.js.map +1 -0
  117. package/dist/lib/prime-schema.d.ts +59 -0
  118. package/dist/lib/prime-schema.js +158 -0
  119. package/dist/lib/prime-schema.js.map +1 -0
  120. package/dist/lib/query-builder.d.ts +10 -0
  121. package/dist/lib/query-builder.js +168 -0
  122. package/dist/lib/query-builder.js.map +1 -0
  123. package/dist/lib/query-commands.d.ts +19 -0
  124. package/dist/lib/query-commands.js +262 -0
  125. package/dist/lib/query-commands.js.map +1 -0
  126. package/dist/lib/session.d.ts +205 -0
  127. package/dist/lib/session.js +1075 -0
  128. package/dist/lib/session.js.map +1 -0
  129. package/dist/lib/tokenize.d.ts +12 -0
  130. package/dist/lib/tokenize.js +48 -0
  131. package/dist/lib/tokenize.js.map +1 -0
  132. package/dist/lib/uiapi.d.ts +109 -0
  133. package/dist/lib/uiapi.js +159 -0
  134. package/dist/lib/uiapi.js.map +1 -0
  135. package/dist/lib/validator.d.ts +27 -0
  136. package/dist/lib/validator.js +100 -0
  137. package/dist/lib/validator.js.map +1 -0
  138. package/dist/lib/variable-promotion.d.ts +69 -0
  139. package/dist/lib/variable-promotion.js +95 -0
  140. package/dist/lib/variable-promotion.js.map +1 -0
  141. package/dist/lib/walker.d.ts +147 -0
  142. package/dist/lib/walker.js +723 -0
  143. package/dist/lib/walker.js.map +1 -0
  144. package/dist/mcp/server.d.ts +7 -0
  145. package/dist/mcp/server.js +34 -0
  146. package/dist/mcp/server.js.map +1 -0
  147. package/dist/mcp/stdio.d.ts +7 -0
  148. package/dist/mcp/stdio.js +25 -0
  149. package/dist/mcp/stdio.js.map +1 -0
  150. package/dist/mcp/tools/echo.d.ts +7 -0
  151. package/dist/mcp/tools/echo.js +17 -0
  152. package/dist/mcp/tools/echo.js.map +1 -0
  153. package/dist/mcp/tools/sf-gql-aggregate.d.ts +11 -0
  154. package/dist/mcp/tools/sf-gql-aggregate.js +75 -0
  155. package/dist/mcp/tools/sf-gql-aggregate.js.map +1 -0
  156. package/dist/mcp/tools/sf-gql-create.d.ts +11 -0
  157. package/dist/mcp/tools/sf-gql-create.js +35 -0
  158. package/dist/mcp/tools/sf-gql-create.js.map +1 -0
  159. package/dist/mcp/tools/sf-gql-delete.d.ts +11 -0
  160. package/dist/mcp/tools/sf-gql-delete.js +31 -0
  161. package/dist/mcp/tools/sf-gql-delete.js.map +1 -0
  162. package/dist/mcp/tools/sf-gql-detail.d.ts +11 -0
  163. package/dist/mcp/tools/sf-gql-detail.js +58 -0
  164. package/dist/mcp/tools/sf-gql-detail.js.map +1 -0
  165. package/dist/mcp/tools/sf-gql-discover.d.ts +9 -0
  166. package/dist/mcp/tools/sf-gql-discover.js +51 -0
  167. package/dist/mcp/tools/sf-gql-discover.js.map +1 -0
  168. package/dist/mcp/tools/sf-gql-list.d.ts +11 -0
  169. package/dist/mcp/tools/sf-gql-list.js +53 -0
  170. package/dist/mcp/tools/sf-gql-list.js.map +1 -0
  171. package/dist/mcp/tools/sf-gql-raw.d.ts +11 -0
  172. package/dist/mcp/tools/sf-gql-raw.js +39 -0
  173. package/dist/mcp/tools/sf-gql-raw.js.map +1 -0
  174. package/dist/mcp/tools/sf-gql-update.d.ts +11 -0
  175. package/dist/mcp/tools/sf-gql-update.js +35 -0
  176. package/dist/mcp/tools/sf-gql-update.js.map +1 -0
  177. package/dist/mcp/tools/shared/zod-schemas.d.ts +49 -0
  178. package/dist/mcp/tools/shared/zod-schemas.js +46 -0
  179. package/dist/mcp/tools/shared/zod-schemas.js.map +1 -0
  180. package/package.json +36 -0
  181. package/ralph-loop.sh +120 -0
  182. package/scripts/smoke-orderby.sh +190 -0
  183. package/src/__tests__/helpers/prime-deps.ts +46 -0
  184. package/src/__tests__/helpers/schema.ts +73 -0
  185. package/src/__tests__/helpers/stdout.ts +33 -0
  186. package/src/__tests__/setup.ts +19 -0
  187. package/src/cli.ts +764 -0
  188. package/src/commands/__tests__/query.spec.ts +137 -0
  189. package/src/commands/args.ts +306 -0
  190. package/src/commands/build.ts +288 -0
  191. package/src/commands/connect.ts +60 -0
  192. package/src/commands/describe.ts +246 -0
  193. package/src/commands/meta.ts +171 -0
  194. package/src/commands/navigate.ts +134 -0
  195. package/src/commands/query-helpers.ts +1202 -0
  196. package/src/commands/query.ts +1085 -0
  197. package/src/commands/review.ts +670 -0
  198. package/src/commands/session-mgmt.ts +256 -0
  199. package/src/commands/type.ts +437 -0
  200. package/src/commands/validate-input.ts +38 -0
  201. package/src/intent/__tests__/build-aggregate.spec.ts +931 -0
  202. package/src/intent/__tests__/build-create-validation.spec.ts +135 -0
  203. package/src/intent/__tests__/build-delete.spec.ts +121 -0
  204. package/src/intent/__tests__/build-detail.spec.ts +333 -0
  205. package/src/intent/__tests__/build-discover.spec.ts +432 -0
  206. package/src/intent/__tests__/build-list.spec.ts +284 -0
  207. package/src/intent/__tests__/build-mutation.spec.ts +108 -0
  208. package/src/intent/__tests__/build-output.spec.ts +55 -0
  209. package/src/intent/__tests__/build-raw.spec.ts +153 -0
  210. package/src/intent/__tests__/build-update-validation.spec.ts +134 -0
  211. package/src/intent/build-aggregate.ts +182 -0
  212. package/src/intent/build-create.ts +19 -0
  213. package/src/intent/build-delete.ts +62 -0
  214. package/src/intent/build-detail.ts +95 -0
  215. package/src/intent/build-discover.ts +199 -0
  216. package/src/intent/build-list.ts +91 -0
  217. package/src/intent/build-mutation.ts +75 -0
  218. package/src/intent/build-output.ts +72 -0
  219. package/src/intent/build-raw.ts +61 -0
  220. package/src/intent/build-update.ts +19 -0
  221. package/src/intent/get-schema-with-priming.ts +43 -0
  222. package/src/intent/select-child-relationship.ts +48 -0
  223. package/src/intent/types.ts +181 -0
  224. package/src/lib/__tests__/apply-command.parity.spec.ts +97 -0
  225. package/src/lib/__tests__/apply-command.spec.ts +171 -0
  226. package/src/lib/__tests__/auth.spec.ts +292 -0
  227. package/src/lib/__tests__/formatter.spec.ts +86 -0
  228. package/src/lib/__tests__/graphql-name.spec.ts +64 -0
  229. package/src/lib/__tests__/introspect.spec.ts +399 -0
  230. package/src/lib/__tests__/object-info.spec.ts +124 -0
  231. package/src/lib/__tests__/path-selection.spec.ts +219 -0
  232. package/src/lib/__tests__/prime-schema.spec.ts +269 -0
  233. package/src/lib/__tests__/query-builder.spec.ts +95 -0
  234. package/src/lib/__tests__/query-commands.spec.ts +74 -0
  235. package/src/lib/__tests__/session.spec.ts +292 -0
  236. package/src/lib/__tests__/tokenize.spec.ts +33 -0
  237. package/src/lib/__tests__/uiapi.spec.ts +192 -0
  238. package/src/lib/__tests__/variable-promotion.spec.ts +211 -0
  239. package/src/lib/__tests__/walker.spec.ts +250 -0
  240. package/src/lib/apply-command.ts +286 -0
  241. package/src/lib/auth.ts +157 -0
  242. package/src/lib/codegen.ts +899 -0
  243. package/src/lib/command-registry.ts +434 -0
  244. package/src/lib/formatter.ts +587 -0
  245. package/src/lib/fs-utils.ts +46 -0
  246. package/src/lib/graphql-name.ts +35 -0
  247. package/src/lib/interactive.ts +634 -0
  248. package/src/lib/introspect.ts +320 -0
  249. package/src/lib/object-info.ts +409 -0
  250. package/src/lib/path-selection.ts +162 -0
  251. package/src/lib/prime-schema.ts +195 -0
  252. package/src/lib/query-builder.ts +193 -0
  253. package/src/lib/query-commands.ts +290 -0
  254. package/src/lib/session.ts +1304 -0
  255. package/src/lib/tokenize.ts +43 -0
  256. package/src/lib/uiapi.ts +176 -0
  257. package/src/lib/validator.ts +143 -0
  258. package/src/lib/variable-promotion.ts +145 -0
  259. package/src/lib/walker.ts +975 -0
  260. package/src/mcp/__tests__/server.spec.ts +155 -0
  261. package/src/mcp/server.ts +38 -0
  262. package/src/mcp/stdio.ts +29 -0
  263. package/src/mcp/tools/__tests__/sf-gql-aggregate.spec.ts +173 -0
  264. package/src/mcp/tools/__tests__/sf-gql-create.spec.ts +235 -0
  265. package/src/mcp/tools/__tests__/sf-gql-delete.spec.ts +194 -0
  266. package/src/mcp/tools/__tests__/sf-gql-detail.spec.ts +246 -0
  267. package/src/mcp/tools/__tests__/sf-gql-discover.spec.ts +320 -0
  268. package/src/mcp/tools/__tests__/sf-gql-list.spec.ts +128 -0
  269. package/src/mcp/tools/__tests__/sf-gql-raw.spec.ts +141 -0
  270. package/src/mcp/tools/__tests__/sf-gql-update.spec.ts +207 -0
  271. package/src/mcp/tools/echo.ts +24 -0
  272. package/src/mcp/tools/sf-gql-aggregate.ts +102 -0
  273. package/src/mcp/tools/sf-gql-create.ts +55 -0
  274. package/src/mcp/tools/sf-gql-delete.ts +49 -0
  275. package/src/mcp/tools/sf-gql-detail.ts +85 -0
  276. package/src/mcp/tools/sf-gql-discover.ts +67 -0
  277. package/src/mcp/tools/sf-gql-list.ts +73 -0
  278. package/src/mcp/tools/sf-gql-raw.ts +56 -0
  279. package/src/mcp/tools/sf-gql-update.ts +55 -0
  280. package/src/mcp/tools/shared/zod-schemas.ts +55 -0
  281. package/tsconfig.json +18 -0
  282. package/vitest.config.ts +14 -0
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import fs from "node:fs";
8
+ import os from "node:os";
9
+ import path from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
11
+
12
+ vi.mock("@salesforce/core", () => ({
13
+ Org: { create: vi.fn() },
14
+ AuthInfo: { listAllAuthorizations: vi.fn() },
15
+ }));
16
+
17
+ const { Org, AuthInfo } = await import("@salesforce/core");
18
+ const { clearOrgAuthCache, getOrgAuth, listOrgs } = await import("../auth.js");
19
+
20
+ const orgCreateMock = vi.mocked(Org.create);
21
+ // AuthInfo.listAllAuthorizations is exercised by listOrgs tests (re-enabled in Task 2)
22
+ const listAuthsMock = vi.mocked(AuthInfo.listAllAuthorizations);
23
+
24
+ function mockOrg(
25
+ fields: Record<string, unknown>,
26
+ opts: { accessToken?: string | null; instanceUrl?: string } = {},
27
+ ) {
28
+ return {
29
+ getConnection: () => ({
30
+ instanceUrl: opts.instanceUrl ?? "https://example.my.salesforce.com/",
31
+ accessToken: opts.accessToken === undefined ? "00Dxx!token" : opts.accessToken,
32
+ getAuthInfo: () => ({ getFields: () => fields }),
33
+ }),
34
+ } as unknown as Awaited<ReturnType<typeof Org.create>>;
35
+ }
36
+
37
+ describe("auth", () => {
38
+ beforeEach(() => {
39
+ clearOrgAuthCache();
40
+ orgCreateMock.mockReset();
41
+ listAuthsMock.mockReset();
42
+ });
43
+
44
+ afterEach(() => {
45
+ clearOrgAuthCache();
46
+ });
47
+
48
+ describe("getOrgAuth alias validation", () => {
49
+ const invalidAliases: [string, string][] = [
50
+ ["semicolon", "good;rm -rf ~"],
51
+ ["dollar", "good$(whoami)"],
52
+ ["backtick", "good`whoami`"],
53
+ ["newline", "good\nrm"],
54
+ ["space", "good org"],
55
+ ["leading hyphen (flag injection)", "-foo"],
56
+ ["leading dot", ".foo"],
57
+ ["embedded space in email", "user @example.com"],
58
+ ["empty string", ""],
59
+ ["over 253 chars", "a".repeat(254)],
60
+ ];
61
+
62
+ for (const [label, alias] of invalidAliases) {
63
+ it(`rejects invalid alias: ${label}`, async () => {
64
+ await expect(getOrgAuth(alias)).rejects.toThrow(/alias or username/i);
65
+ expect(orgCreateMock).not.toHaveBeenCalled();
66
+ });
67
+ }
68
+
69
+ it("does not echo the offending alias verbatim into the error message", async () => {
70
+ const malicious = "evil;rm -rf ~";
71
+ await expect(getOrgAuth(malicious)).rejects.toThrow();
72
+ try {
73
+ await getOrgAuth(malicious);
74
+ } catch (err) {
75
+ expect((err as Error).message).not.toContain(malicious);
76
+ }
77
+ });
78
+ });
79
+
80
+ describe("getOrgAuth username/email acceptance", () => {
81
+ const validIdentifiers: [string, string][] = [
82
+ ["simple alias", "MyOrg"],
83
+ ["alias with hyphen and underscore", "MyOrg-1_2"],
84
+ ["plain email username", "user@example.com"],
85
+ ["email with sandbox suffix", "admin@my-company.com.sandbox"],
86
+ ["email with plus tag and complex domain", "dev+build1@scratch-org-12.example.com"],
87
+ ["org id", "00D5g00000ABCDE"],
88
+ ];
89
+
90
+ for (const [label, identifier] of validIdentifiers) {
91
+ it(`accepts valid identifier: ${label}`, async () => {
92
+ orgCreateMock.mockResolvedValueOnce(
93
+ mockOrg({ username: "user@example.com", orgId: "00Dxx0000000000" }),
94
+ );
95
+ await expect(getOrgAuth(identifier)).resolves.not.toThrow();
96
+ expect(orgCreateMock).toHaveBeenCalledWith({ aliasOrUsername: identifier });
97
+ });
98
+ }
99
+ });
100
+
101
+ describe("getOrgAuth happy path", () => {
102
+ it("resolves via Org.create and maps fields", async () => {
103
+ orgCreateMock.mockResolvedValueOnce(
104
+ mockOrg({ username: "user@example.com", orgId: "00Dxx0000000000" }),
105
+ );
106
+ const auth = await getOrgAuth("MyOrg-1_2");
107
+ expect(orgCreateMock).toHaveBeenCalledWith({ aliasOrUsername: "MyOrg-1_2" });
108
+ expect(auth.accessToken).toBe("00Dxx!token");
109
+ expect(auth.instanceUrl).toBe("https://example.my.salesforce.com"); // trailing slash stripped
110
+ expect(auth.username).toBe("user@example.com");
111
+ expect(auth.orgId).toBe("00Dxx0000000000");
112
+ expect(auth.alias).toBe("MyOrg-1_2");
113
+ });
114
+
115
+ it("memoizes: a second call does not re-create the org", async () => {
116
+ orgCreateMock.mockResolvedValueOnce(mockOrg({ username: "u", orgId: "00D" }));
117
+ await getOrgAuth("CachedOrg");
118
+ await getOrgAuth("CachedOrg");
119
+ expect(orgCreateMock).toHaveBeenCalledTimes(1);
120
+ });
121
+
122
+ it("throws a re-auth hint when access token is missing", async () => {
123
+ orgCreateMock.mockResolvedValueOnce(
124
+ mockOrg({ username: "u", orgId: "00D" }, { accessToken: null }),
125
+ );
126
+ await expect(getOrgAuth("NoToken")).rejects.toThrow(/sf org login/i);
127
+ });
128
+
129
+ it("wraps Org.create failure with a re-auth hint and preserves the cause", async () => {
130
+ orgCreateMock.mockRejectedValue(new Error("NamedOrgNotFound"));
131
+ await expect(getOrgAuth("MissingOrg")).rejects.toThrow(/sf org login web --alias MissingOrg/);
132
+ await expect(getOrgAuth("MissingOrg")).rejects.toThrow(/NamedOrgNotFound/);
133
+ });
134
+
135
+ it('falls back to "unknown" when username/orgId are absent', async () => {
136
+ orgCreateMock.mockResolvedValueOnce(mockOrg({})); // empty fields
137
+ const auth = await getOrgAuth("SparseOrg");
138
+ expect(auth.username).toBe("unknown");
139
+ expect(auth.orgId).toBe("unknown");
140
+ });
141
+
142
+ it("accepts an alias starting with underscore", async () => {
143
+ orgCreateMock.mockResolvedValueOnce(
144
+ mockOrg({ username: "user@example.com", orgId: "00Dxx0000000000" }),
145
+ );
146
+ await expect(getOrgAuth("_internal")).resolves.not.toThrow();
147
+ expect(orgCreateMock).toHaveBeenCalledTimes(1);
148
+ });
149
+ });
150
+
151
+ describe("listOrgs", () => {
152
+ it("maps scratch and non-scratch and prefers alias over username", async () => {
153
+ listAuthsMock.mockResolvedValueOnce([
154
+ {
155
+ aliases: ["Prod"],
156
+ username: "p@example.com",
157
+ instanceUrl: "https://p.my.salesforce.com/",
158
+ orgId: "00DP",
159
+ isScratchOrg: false,
160
+ isExpired: false,
161
+ oauthMethod: "web",
162
+ configs: [],
163
+ },
164
+ {
165
+ aliases: ["Scr"],
166
+ username: "s@example.com",
167
+ instanceUrl: "https://s.my.salesforce.com",
168
+ orgId: "00DS",
169
+ isScratchOrg: true,
170
+ isExpired: false,
171
+ oauthMethod: "web",
172
+ configs: [],
173
+ },
174
+ ] as never);
175
+ const orgs = await listOrgs();
176
+ expect(orgs).toHaveLength(2);
177
+ expect(orgs[0]).toMatchObject({
178
+ alias: "Prod",
179
+ type: "non-scratch",
180
+ instanceUrl: "https://p.my.salesforce.com",
181
+ });
182
+ expect(orgs[1]).toMatchObject({ alias: "Scr", type: "scratch" });
183
+ });
184
+
185
+ it("falls back to username when an org has no alias", async () => {
186
+ listAuthsMock.mockResolvedValueOnce([
187
+ {
188
+ aliases: null,
189
+ username: "noalias@example.com",
190
+ instanceUrl: "https://n.my.salesforce.com",
191
+ orgId: "00DN",
192
+ isScratchOrg: false,
193
+ isExpired: false,
194
+ oauthMethod: "web",
195
+ configs: [],
196
+ },
197
+ ] as never);
198
+ const orgs = await listOrgs();
199
+ expect(orgs).toHaveLength(1);
200
+ expect(orgs[0].alias).toBe("noalias@example.com");
201
+ expect(orgs[0].username).toBe("noalias@example.com");
202
+ });
203
+
204
+ it("skips orgs with neither alias nor username", async () => {
205
+ listAuthsMock.mockResolvedValueOnce([
206
+ {
207
+ aliases: null,
208
+ username: "",
209
+ instanceUrl: "https://x.my.salesforce.com",
210
+ orgId: "00DX",
211
+ isScratchOrg: false,
212
+ isExpired: false,
213
+ oauthMethod: "web",
214
+ configs: [],
215
+ },
216
+ ] as never);
217
+ expect(await listOrgs()).toHaveLength(0);
218
+ });
219
+
220
+ it("marks isConnected=true when a session's orgAlias matches one of the org's aliases", async () => {
221
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-conn-"));
222
+ const sessionsDir = path.join(home, "sessions");
223
+ fs.mkdirSync(sessionsDir, { recursive: true });
224
+ fs.writeFileSync(path.join(sessionsDir, "s1.json"), JSON.stringify({ orgAlias: "Prod" }));
225
+ const prev = process.env.GRAPHITI_HOME;
226
+ process.env.GRAPHITI_HOME = home;
227
+ try {
228
+ listAuthsMock.mockResolvedValueOnce([
229
+ {
230
+ aliases: ["Prod"],
231
+ username: "p@example.com",
232
+ instanceUrl: "https://p.my.salesforce.com",
233
+ orgId: "00DP",
234
+ isScratchOrg: false,
235
+ isExpired: false,
236
+ oauthMethod: "web",
237
+ configs: [],
238
+ },
239
+ {
240
+ aliases: ["Other"],
241
+ username: "o@example.com",
242
+ instanceUrl: "https://o.my.salesforce.com",
243
+ orgId: "00DO",
244
+ isScratchOrg: false,
245
+ isExpired: false,
246
+ oauthMethod: "web",
247
+ configs: [],
248
+ },
249
+ ] as never);
250
+ const orgs = await listOrgs();
251
+ expect(orgs.find((o) => o.alias === "Prod")?.isConnected).toBe(true);
252
+ expect(orgs.find((o) => o.alias === "Other")?.isConnected).toBe(false);
253
+ } finally {
254
+ if (prev === undefined) delete process.env.GRAPHITI_HOME;
255
+ else process.env.GRAPHITI_HOME = prev;
256
+ fs.rmSync(home, { recursive: true, force: true });
257
+ }
258
+ });
259
+
260
+ it("marks isConnected=true when a session's orgAlias matches the org's username (no alias)", async () => {
261
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-conn-"));
262
+ const sessionsDir = path.join(home, "sessions");
263
+ fs.mkdirSync(sessionsDir, { recursive: true });
264
+ fs.writeFileSync(
265
+ path.join(sessionsDir, "s1.json"),
266
+ JSON.stringify({ orgAlias: "user@example.com" }),
267
+ );
268
+ const prev = process.env.GRAPHITI_HOME;
269
+ process.env.GRAPHITI_HOME = home;
270
+ try {
271
+ listAuthsMock.mockResolvedValueOnce([
272
+ {
273
+ aliases: null,
274
+ username: "user@example.com",
275
+ instanceUrl: "https://u.my.salesforce.com",
276
+ orgId: "00DU",
277
+ isScratchOrg: false,
278
+ isExpired: false,
279
+ oauthMethod: "web",
280
+ configs: [],
281
+ },
282
+ ] as never);
283
+ const orgs = await listOrgs();
284
+ expect(orgs[0].isConnected).toBe(true);
285
+ } finally {
286
+ if (prev === undefined) delete process.env.GRAPHITI_HOME;
287
+ else process.env.GRAPHITI_HOME = prev;
288
+ fs.rmSync(home, { recursive: true, force: true });
289
+ }
290
+ });
291
+ });
292
+ });
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import { describe, expect, it } from "vitest";
8
+ import { formatDirectoryListing, formatTypeInfo } from "../formatter.js";
9
+ import type { FieldInfo } from "../walker.js";
10
+
11
+ function mockField(name: string): FieldInfo {
12
+ return {
13
+ name,
14
+ typeName: `${name}Connection`,
15
+ typeKind: "OBJECT",
16
+ isNonNull: false,
17
+ isList: false,
18
+ description: null,
19
+ args: [],
20
+ };
21
+ }
22
+
23
+ describe("formatter", () => {
24
+ describe("formatDirectoryListing", () => {
25
+ it("hides Data Cloud fields and shows hint", () => {
26
+ const result = {
27
+ type: {} as never,
28
+ typeName: "RecordQuery",
29
+ kind: "OBJECT" as const,
30
+ fields: [
31
+ mockField("Account"),
32
+ mockField("ssot__Account__dlm"),
33
+ mockField("ssot__Contact__dlm"),
34
+ mockField("Case"),
35
+ ],
36
+ args: [],
37
+ possibleTypes: [],
38
+ isLeaf: false,
39
+ inMutationRecord: false,
40
+ mutationHiddenFields: [],
41
+ };
42
+ const output = formatDirectoryListing(result, { all: true });
43
+ expect(output).not.toContain("ssot__Account__dlm");
44
+ expect(output).not.toContain("ssot__Contact__dlm");
45
+ expect(output).toContain("Account");
46
+ expect(output).toContain("Case");
47
+ expect(output).toContain("2 Data Cloud fields hidden");
48
+ });
49
+
50
+ it("shows Data Cloud fields with dataCloud opt", () => {
51
+ const result = {
52
+ type: {} as never,
53
+ typeName: "RecordQuery",
54
+ kind: "OBJECT" as const,
55
+ fields: [mockField("Account"), mockField("ssot__Account__dlm"), mockField("Case")],
56
+ args: [],
57
+ possibleTypes: [],
58
+ isLeaf: false,
59
+ inMutationRecord: false,
60
+ mutationHiddenFields: [],
61
+ };
62
+ const output = formatDirectoryListing(result, { all: true, dataCloud: true });
63
+ expect(output).toContain("ssot__Account__dlm");
64
+ expect(output).not.toContain("Data Cloud fields hidden");
65
+ });
66
+ });
67
+
68
+ describe("formatTypeInfo", () => {
69
+ it("hides Data Cloud fields and shows hint", () => {
70
+ const info = {
71
+ name: "RecordQuery",
72
+ kind: "OBJECT" as const,
73
+ description: null,
74
+ fields: [mockField("Account"), mockField("ssot__Account__dlm"), mockField("Case")],
75
+ inputFields: [],
76
+ enumValues: [],
77
+ possibleTypes: [],
78
+ interfaces: [],
79
+ };
80
+ const output = formatTypeInfo(info, undefined, {});
81
+ expect(output).not.toContain("ssot__Account__dlm");
82
+ expect(output).toContain("Account");
83
+ expect(output).toContain("1 Data Cloud fields hidden");
84
+ });
85
+ });
86
+ });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import { describe, expect, it } from "vitest";
8
+ import { assertGraphqlName, GRAPHQL_NAME_RE } from "../graphql-name.js";
9
+
10
+ /**
11
+ * Single source of truth for the GraphQL Name guard shared by every intent
12
+ * builder. The exhaustive valid/invalid matrix lives here; each builder spec
13
+ * keeps just one representative case proving it wires the guard in.
14
+ */
15
+ describe("lib/graphql-name", () => {
16
+ describe("assertGraphqlName", () => {
17
+ it.each(["Account", "Custom_Object__c", "_Foo", "_", "a", "a1_2b", "CreateAccount"])(
18
+ "accepts the valid GraphQL Name %j",
19
+ (value) => {
20
+ expect(() => assertGraphqlName(value, "buildX", "operationName")).not.toThrow();
21
+ },
22
+ );
23
+
24
+ it.each([
25
+ "", // empty
26
+ "1Bad", // leading digit
27
+ "my-op", // hyphen
28
+ "has spaces", // internal space
29
+ "$op", // leading $ (no stripping in the operation-name position)
30
+ " ", // whitespace only
31
+ " leading", // leading space
32
+ "trailing ", // trailing space
33
+ "X { uiapi { __typename } } query Y", // the injection payload from W-22731343
34
+ "}}__schema{types{name", // brace injection
35
+ ])("rejects the invalid GraphQL Name %j", (value) => {
36
+ expect(() => assertGraphqlName(value, "buildX", "operationName")).toThrow(
37
+ /is not a valid GraphQL Name/,
38
+ );
39
+ });
40
+
41
+ it("throws the exact builder- and field-prefixed message", () => {
42
+ expect(() => assertGraphqlName("my-op", "buildList", "operationName")).toThrow(
43
+ "buildList: operationName 'my-op' is not a valid GraphQL Name (must match /^[A-Za-z_][A-Za-z0-9_]*$/)",
44
+ );
45
+ });
46
+
47
+ it("uses the supplied builder and field labels verbatim", () => {
48
+ expect(() => assertGraphqlName("1x", "buildRaw", "typeName")).toThrow(
49
+ /^buildRaw: typeName '1x' is not a valid GraphQL Name/,
50
+ );
51
+ });
52
+ });
53
+
54
+ describe("GRAPHQL_NAME_RE", () => {
55
+ it("matches the GraphQL Name production and rejects non-conforming names", () => {
56
+ expect(GRAPHQL_NAME_RE.test("Account")).toBe(true);
57
+ expect(GRAPHQL_NAME_RE.test("_Foo")).toBe(true);
58
+ expect(GRAPHQL_NAME_RE.test("Custom_Object__c")).toBe(true);
59
+ expect(GRAPHQL_NAME_RE.test("1Bad")).toBe(false);
60
+ expect(GRAPHQL_NAME_RE.test("has spaces")).toBe(false);
61
+ expect(GRAPHQL_NAME_RE.test("")).toBe(false);
62
+ });
63
+ });
64
+ });