@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.
- package/AGENT_GUIDE.md +424 -0
- package/CHANGELOG.md +448 -0
- package/LICENSE.txt +82 -0
- package/README.md +204 -0
- package/TASK.md +249 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +683 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/args.d.ts +13 -0
- package/dist/commands/args.js +207 -0
- package/dist/commands/args.js.map +1 -0
- package/dist/commands/build.d.ts +11 -0
- package/dist/commands/build.js +209 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/connect.d.ts +8 -0
- package/dist/commands/connect.js +55 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/describe.d.ts +6 -0
- package/dist/commands/describe.js +229 -0
- package/dist/commands/describe.js.map +1 -0
- package/dist/commands/meta.d.ts +9 -0
- package/dist/commands/meta.js +140 -0
- package/dist/commands/meta.js.map +1 -0
- package/dist/commands/navigate.d.ts +14 -0
- package/dist/commands/navigate.js +105 -0
- package/dist/commands/navigate.js.map +1 -0
- package/dist/commands/query-helpers.d.ts +80 -0
- package/dist/commands/query-helpers.js +865 -0
- package/dist/commands/query-helpers.js.map +1 -0
- package/dist/commands/query.d.ts +26 -0
- package/dist/commands/query.js +901 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/review.d.ts +18 -0
- package/dist/commands/review.js +533 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/session-mgmt.d.ts +25 -0
- package/dist/commands/session-mgmt.js +206 -0
- package/dist/commands/session-mgmt.js.map +1 -0
- package/dist/commands/type.d.ts +6 -0
- package/dist/commands/type.js +342 -0
- package/dist/commands/type.js.map +1 -0
- package/dist/commands/validate-input.d.ts +6 -0
- package/dist/commands/validate-input.js +32 -0
- package/dist/commands/validate-input.js.map +1 -0
- package/dist/intent/build-aggregate.d.ts +33 -0
- package/dist/intent/build-aggregate.js +134 -0
- package/dist/intent/build-aggregate.js.map +1 -0
- package/dist/intent/build-create.d.ts +14 -0
- package/dist/intent/build-create.js +16 -0
- package/dist/intent/build-create.js.map +1 -0
- package/dist/intent/build-delete.d.ts +30 -0
- package/dist/intent/build-delete.js +53 -0
- package/dist/intent/build-delete.js.map +1 -0
- package/dist/intent/build-detail.d.ts +32 -0
- package/dist/intent/build-detail.js +80 -0
- package/dist/intent/build-detail.js.map +1 -0
- package/dist/intent/build-discover.d.ts +30 -0
- package/dist/intent/build-discover.js +149 -0
- package/dist/intent/build-discover.js.map +1 -0
- package/dist/intent/build-list.d.ts +28 -0
- package/dist/intent/build-list.js +75 -0
- package/dist/intent/build-list.js.map +1 -0
- package/dist/intent/build-mutation.d.ts +23 -0
- package/dist/intent/build-mutation.js +54 -0
- package/dist/intent/build-mutation.js.map +1 -0
- package/dist/intent/build-output.d.ts +27 -0
- package/dist/intent/build-output.js +60 -0
- package/dist/intent/build-output.js.map +1 -0
- package/dist/intent/build-raw.d.ts +23 -0
- package/dist/intent/build-raw.js +54 -0
- package/dist/intent/build-raw.js.map +1 -0
- package/dist/intent/build-update.d.ts +14 -0
- package/dist/intent/build-update.js +16 -0
- package/dist/intent/build-update.js.map +1 -0
- package/dist/intent/get-schema-with-priming.d.ts +26 -0
- package/dist/intent/get-schema-with-priming.js +32 -0
- package/dist/intent/get-schema-with-priming.js.map +1 -0
- package/dist/intent/select-child-relationship.d.ts +19 -0
- package/dist/intent/select-child-relationship.js +38 -0
- package/dist/intent/select-child-relationship.js.map +1 -0
- package/dist/intent/types.d.ts +159 -0
- package/dist/intent/types.js +21 -0
- package/dist/intent/types.js.map +1 -0
- package/dist/lib/apply-command.d.ts +15 -0
- package/dist/lib/apply-command.js +238 -0
- package/dist/lib/apply-command.js.map +1 -0
- package/dist/lib/auth.d.ts +38 -0
- package/dist/lib/auth.js +113 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/codegen.d.ts +32 -0
- package/dist/lib/codegen.js +700 -0
- package/dist/lib/codegen.js.map +1 -0
- package/dist/lib/command-registry.d.ts +59 -0
- package/dist/lib/command-registry.js +366 -0
- package/dist/lib/command-registry.js.map +1 -0
- package/dist/lib/formatter.d.ts +76 -0
- package/dist/lib/formatter.js +419 -0
- package/dist/lib/formatter.js.map +1 -0
- package/dist/lib/fs-utils.d.ts +23 -0
- package/dist/lib/fs-utils.js +46 -0
- package/dist/lib/fs-utils.js.map +1 -0
- package/dist/lib/graphql-name.d.ts +27 -0
- package/dist/lib/graphql-name.js +32 -0
- package/dist/lib/graphql-name.js.map +1 -0
- package/dist/lib/interactive.d.ts +6 -0
- package/dist/lib/interactive.js +562 -0
- package/dist/lib/interactive.js.map +1 -0
- package/dist/lib/introspect.d.ts +40 -0
- package/dist/lib/introspect.js +280 -0
- package/dist/lib/introspect.js.map +1 -0
- package/dist/lib/object-info.d.ts +79 -0
- package/dist/lib/object-info.js +313 -0
- package/dist/lib/object-info.js.map +1 -0
- package/dist/lib/path-selection.d.ts +50 -0
- package/dist/lib/path-selection.js +146 -0
- package/dist/lib/path-selection.js.map +1 -0
- package/dist/lib/prime-schema.d.ts +59 -0
- package/dist/lib/prime-schema.js +158 -0
- package/dist/lib/prime-schema.js.map +1 -0
- package/dist/lib/query-builder.d.ts +10 -0
- package/dist/lib/query-builder.js +168 -0
- package/dist/lib/query-builder.js.map +1 -0
- package/dist/lib/query-commands.d.ts +19 -0
- package/dist/lib/query-commands.js +262 -0
- package/dist/lib/query-commands.js.map +1 -0
- package/dist/lib/session.d.ts +205 -0
- package/dist/lib/session.js +1075 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/tokenize.d.ts +12 -0
- package/dist/lib/tokenize.js +48 -0
- package/dist/lib/tokenize.js.map +1 -0
- package/dist/lib/uiapi.d.ts +109 -0
- package/dist/lib/uiapi.js +159 -0
- package/dist/lib/uiapi.js.map +1 -0
- package/dist/lib/validator.d.ts +27 -0
- package/dist/lib/validator.js +100 -0
- package/dist/lib/validator.js.map +1 -0
- package/dist/lib/variable-promotion.d.ts +69 -0
- package/dist/lib/variable-promotion.js +95 -0
- package/dist/lib/variable-promotion.js.map +1 -0
- package/dist/lib/walker.d.ts +147 -0
- package/dist/lib/walker.js +723 -0
- package/dist/lib/walker.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +34 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +7 -0
- package/dist/mcp/stdio.js +25 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/tools/echo.d.ts +7 -0
- package/dist/mcp/tools/echo.js +17 -0
- package/dist/mcp/tools/echo.js.map +1 -0
- package/dist/mcp/tools/sf-gql-aggregate.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-aggregate.js +75 -0
- package/dist/mcp/tools/sf-gql-aggregate.js.map +1 -0
- package/dist/mcp/tools/sf-gql-create.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-create.js +35 -0
- package/dist/mcp/tools/sf-gql-create.js.map +1 -0
- package/dist/mcp/tools/sf-gql-delete.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-delete.js +31 -0
- package/dist/mcp/tools/sf-gql-delete.js.map +1 -0
- package/dist/mcp/tools/sf-gql-detail.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-detail.js +58 -0
- package/dist/mcp/tools/sf-gql-detail.js.map +1 -0
- package/dist/mcp/tools/sf-gql-discover.d.ts +9 -0
- package/dist/mcp/tools/sf-gql-discover.js +51 -0
- package/dist/mcp/tools/sf-gql-discover.js.map +1 -0
- package/dist/mcp/tools/sf-gql-list.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-list.js +53 -0
- package/dist/mcp/tools/sf-gql-list.js.map +1 -0
- package/dist/mcp/tools/sf-gql-raw.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-raw.js +39 -0
- package/dist/mcp/tools/sf-gql-raw.js.map +1 -0
- package/dist/mcp/tools/sf-gql-update.d.ts +11 -0
- package/dist/mcp/tools/sf-gql-update.js +35 -0
- package/dist/mcp/tools/sf-gql-update.js.map +1 -0
- package/dist/mcp/tools/shared/zod-schemas.d.ts +49 -0
- package/dist/mcp/tools/shared/zod-schemas.js +46 -0
- package/dist/mcp/tools/shared/zod-schemas.js.map +1 -0
- package/package.json +36 -0
- package/ralph-loop.sh +120 -0
- package/scripts/smoke-orderby.sh +190 -0
- package/src/__tests__/helpers/prime-deps.ts +46 -0
- package/src/__tests__/helpers/schema.ts +73 -0
- package/src/__tests__/helpers/stdout.ts +33 -0
- package/src/__tests__/setup.ts +19 -0
- package/src/cli.ts +764 -0
- package/src/commands/__tests__/query.spec.ts +137 -0
- package/src/commands/args.ts +306 -0
- package/src/commands/build.ts +288 -0
- package/src/commands/connect.ts +60 -0
- package/src/commands/describe.ts +246 -0
- package/src/commands/meta.ts +171 -0
- package/src/commands/navigate.ts +134 -0
- package/src/commands/query-helpers.ts +1202 -0
- package/src/commands/query.ts +1085 -0
- package/src/commands/review.ts +670 -0
- package/src/commands/session-mgmt.ts +256 -0
- package/src/commands/type.ts +437 -0
- package/src/commands/validate-input.ts +38 -0
- package/src/intent/__tests__/build-aggregate.spec.ts +931 -0
- package/src/intent/__tests__/build-create-validation.spec.ts +135 -0
- package/src/intent/__tests__/build-delete.spec.ts +121 -0
- package/src/intent/__tests__/build-detail.spec.ts +333 -0
- package/src/intent/__tests__/build-discover.spec.ts +432 -0
- package/src/intent/__tests__/build-list.spec.ts +284 -0
- package/src/intent/__tests__/build-mutation.spec.ts +108 -0
- package/src/intent/__tests__/build-output.spec.ts +55 -0
- package/src/intent/__tests__/build-raw.spec.ts +153 -0
- package/src/intent/__tests__/build-update-validation.spec.ts +134 -0
- package/src/intent/build-aggregate.ts +182 -0
- package/src/intent/build-create.ts +19 -0
- package/src/intent/build-delete.ts +62 -0
- package/src/intent/build-detail.ts +95 -0
- package/src/intent/build-discover.ts +199 -0
- package/src/intent/build-list.ts +91 -0
- package/src/intent/build-mutation.ts +75 -0
- package/src/intent/build-output.ts +72 -0
- package/src/intent/build-raw.ts +61 -0
- package/src/intent/build-update.ts +19 -0
- package/src/intent/get-schema-with-priming.ts +43 -0
- package/src/intent/select-child-relationship.ts +48 -0
- package/src/intent/types.ts +181 -0
- package/src/lib/__tests__/apply-command.parity.spec.ts +97 -0
- package/src/lib/__tests__/apply-command.spec.ts +171 -0
- package/src/lib/__tests__/auth.spec.ts +292 -0
- package/src/lib/__tests__/formatter.spec.ts +86 -0
- package/src/lib/__tests__/graphql-name.spec.ts +64 -0
- package/src/lib/__tests__/introspect.spec.ts +399 -0
- package/src/lib/__tests__/object-info.spec.ts +124 -0
- package/src/lib/__tests__/path-selection.spec.ts +219 -0
- package/src/lib/__tests__/prime-schema.spec.ts +269 -0
- package/src/lib/__tests__/query-builder.spec.ts +95 -0
- package/src/lib/__tests__/query-commands.spec.ts +74 -0
- package/src/lib/__tests__/session.spec.ts +292 -0
- package/src/lib/__tests__/tokenize.spec.ts +33 -0
- package/src/lib/__tests__/uiapi.spec.ts +192 -0
- package/src/lib/__tests__/variable-promotion.spec.ts +211 -0
- package/src/lib/__tests__/walker.spec.ts +250 -0
- package/src/lib/apply-command.ts +286 -0
- package/src/lib/auth.ts +157 -0
- package/src/lib/codegen.ts +899 -0
- package/src/lib/command-registry.ts +434 -0
- package/src/lib/formatter.ts +587 -0
- package/src/lib/fs-utils.ts +46 -0
- package/src/lib/graphql-name.ts +35 -0
- package/src/lib/interactive.ts +634 -0
- package/src/lib/introspect.ts +320 -0
- package/src/lib/object-info.ts +409 -0
- package/src/lib/path-selection.ts +162 -0
- package/src/lib/prime-schema.ts +195 -0
- package/src/lib/query-builder.ts +193 -0
- package/src/lib/query-commands.ts +290 -0
- package/src/lib/session.ts +1304 -0
- package/src/lib/tokenize.ts +43 -0
- package/src/lib/uiapi.ts +176 -0
- package/src/lib/validator.ts +143 -0
- package/src/lib/variable-promotion.ts +145 -0
- package/src/lib/walker.ts +975 -0
- package/src/mcp/__tests__/server.spec.ts +155 -0
- package/src/mcp/server.ts +38 -0
- package/src/mcp/stdio.ts +29 -0
- package/src/mcp/tools/__tests__/sf-gql-aggregate.spec.ts +173 -0
- package/src/mcp/tools/__tests__/sf-gql-create.spec.ts +235 -0
- package/src/mcp/tools/__tests__/sf-gql-delete.spec.ts +194 -0
- package/src/mcp/tools/__tests__/sf-gql-detail.spec.ts +246 -0
- package/src/mcp/tools/__tests__/sf-gql-discover.spec.ts +320 -0
- package/src/mcp/tools/__tests__/sf-gql-list.spec.ts +128 -0
- package/src/mcp/tools/__tests__/sf-gql-raw.spec.ts +141 -0
- package/src/mcp/tools/__tests__/sf-gql-update.spec.ts +207 -0
- package/src/mcp/tools/echo.ts +24 -0
- package/src/mcp/tools/sf-gql-aggregate.ts +102 -0
- package/src/mcp/tools/sf-gql-create.ts +55 -0
- package/src/mcp/tools/sf-gql-delete.ts +49 -0
- package/src/mcp/tools/sf-gql-detail.ts +85 -0
- package/src/mcp/tools/sf-gql-discover.ts +67 -0
- package/src/mcp/tools/sf-gql-list.ts +73 -0
- package/src/mcp/tools/sf-gql-raw.ts +56 -0
- package/src/mcp/tools/sf-gql-update.ts +55 -0
- package/src/mcp/tools/shared/zod-schemas.ts +55 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ⚠️ THIRD COPY of the select/set/var token-parsing logic.
|
|
8
|
+
//
|
|
9
|
+
// The canonical parser lives inline in `commands/query.ts` →
|
|
10
|
+
// `dispatchQueryCommand` (the `select`/`set`/`var` switch cases). It is ALSO
|
|
11
|
+
// copy-pasted into `parseInlineSetArgs` (commands/query.ts, used by `clone`).
|
|
12
|
+
// This is copy #3: the disk/console-free path the MCP `sf_gql_raw` tool needs
|
|
13
|
+
// (FR-14 transient in-memory session — incompatible with the dispatcher's
|
|
14
|
+
// loadSession/saveSession/console coupling).
|
|
15
|
+
//
|
|
16
|
+
// FOLLOW-UP STORY (post-spec cleanup): extract one shared pure parser and
|
|
17
|
+
// collapse all three copies. See W-22455777 PR.
|
|
18
|
+
//
|
|
19
|
+
// Console handling: the ported dispatcher helpers (defineAtPath,
|
|
20
|
+
// emitObjectInfoWarnings) still emit console.log/info feedback. applyCommand
|
|
21
|
+
// silences the stdout-bound console methods (log/info) for the duration of each
|
|
22
|
+
// call so they cannot corrupt the MCP stdio JSON-RPC frame stream (see
|
|
23
|
+
// mcp/stdio.ts); console.warn/error (stderr) are left intact. Net effect: the
|
|
24
|
+
// CLI's human-feedback lines and soft signals (ambiguous-parse hints) are
|
|
25
|
+
// suppressed on the MCP path — acceptable for v1; revisit if eval needs them.
|
|
26
|
+
|
|
27
|
+
import { type GraphQLSchema } from "graphql";
|
|
28
|
+
import { type QuerySession } from "./session.js";
|
|
29
|
+
import { tokenizeCommand } from "./tokenize.js";
|
|
30
|
+
import { defineAtPath } from "../commands/args.js";
|
|
31
|
+
import { selectLeafInSession, assignViaPath } from "../commands/query-helpers.js";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Apply one CLI-style command to a transient in-memory session. Mutates
|
|
35
|
+
* `session` in place. Throws on any failure (unknown verb, malformed tokens,
|
|
36
|
+
* unresolvable path) so the caller can fail the whole `commands[]` sequence.
|
|
37
|
+
*
|
|
38
|
+
* v1 supports `select`, `set`, and `var` (FR-12.1, live CLI grammar).
|
|
39
|
+
*/
|
|
40
|
+
export function applyCommand(session: QuerySession, schema: GraphQLSchema, cmd: string): void {
|
|
41
|
+
const tokens = tokenizeCommand(cmd);
|
|
42
|
+
if (tokens.length === 0) {
|
|
43
|
+
throw new Error("empty command");
|
|
44
|
+
}
|
|
45
|
+
const verb = tokens[0];
|
|
46
|
+
const rest = tokens.slice(1);
|
|
47
|
+
|
|
48
|
+
// The ported helpers (defineAtPath, assignViaPath→emitObjectInfoWarnings,
|
|
49
|
+
// selectLeafInSession) emit console.log/info for CLI feedback — i.e. to
|
|
50
|
+
// STDOUT. In an MCP stdio server stdout is the JSON-RPC frame stream
|
|
51
|
+
// (see mcp/stdio.ts); any stray write corrupts it. Silence the stdout-bound
|
|
52
|
+
// console methods for the duration. console.warn/error (stderr) are left
|
|
53
|
+
// intact — stderr is safe and may carry diagnostics. applyCommand is
|
|
54
|
+
// synchronous, so the finally-restore cannot interleave with other work.
|
|
55
|
+
const origLog = console.log;
|
|
56
|
+
const origInfo = console.info;
|
|
57
|
+
const noop = (): void => {
|
|
58
|
+
/* swallow stdout-bound CLI feedback — see comment above */
|
|
59
|
+
};
|
|
60
|
+
console.log = noop;
|
|
61
|
+
console.info = noop;
|
|
62
|
+
try {
|
|
63
|
+
switch (verb) {
|
|
64
|
+
case "select":
|
|
65
|
+
applySelect(session, rest);
|
|
66
|
+
return;
|
|
67
|
+
case "set":
|
|
68
|
+
applySet(session, rest);
|
|
69
|
+
return;
|
|
70
|
+
case "var":
|
|
71
|
+
applyVar(session, rest);
|
|
72
|
+
return;
|
|
73
|
+
default:
|
|
74
|
+
throw new Error(`unknown command '${verb}' (sf_gql_raw v1 supports: select, set, var)`);
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
console.log = origLog;
|
|
78
|
+
console.info = origInfo;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function applySelect(session: QuerySession, rest: string[]): void {
|
|
83
|
+
// Ported from commands/query.ts `select` case. Splits each token into
|
|
84
|
+
// `spec[:alias]`, honoring `.on:Type` fragment syntax (a trailing `:alias`
|
|
85
|
+
// only counts when it has no `.`/`/`/`:` chars). Console feedback and the
|
|
86
|
+
// CLI's `--optional` / `opts.as` paths are intentionally dropped (FR-12 v1).
|
|
87
|
+
const selectItems: { spec: string; alias?: string }[] = [];
|
|
88
|
+
for (const rawToken of rest) {
|
|
89
|
+
if (rawToken.startsWith("--")) continue;
|
|
90
|
+
const subTokens = rawToken.includes(" ") ? rawToken.split(/\s+/).filter(Boolean) : [rawToken];
|
|
91
|
+
for (const token of subTokens) {
|
|
92
|
+
const colonIdx = token.lastIndexOf(":");
|
|
93
|
+
const afterColon = colonIdx > 0 ? token.slice(colonIdx + 1) : "";
|
|
94
|
+
if (colonIdx > 0 && afterColon.length > 0 && !/[./:]/.test(afterColon)) {
|
|
95
|
+
selectItems.push({ spec: token.slice(0, colonIdx), alias: afterColon });
|
|
96
|
+
} else {
|
|
97
|
+
selectItems.push({ spec: token });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (selectItems.length === 0) {
|
|
102
|
+
throw new Error("select requires at least one <path[:alias]>");
|
|
103
|
+
}
|
|
104
|
+
for (const item of selectItems) {
|
|
105
|
+
selectLeafInSession(session, item.spec, item.alias);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function applySet(session: QuerySession, rest: string[]): void {
|
|
110
|
+
// Ported from commands/query.ts `set` case (the 170-line shorthand parser).
|
|
111
|
+
// Transform: queryAssign→assignViaPath, queryDefine→defineAtPath, cursor
|
|
112
|
+
// pageInfo→selectLeafInSession, console+return→throw, drop --default's
|
|
113
|
+
// console feedback. Parity-tested against the dispatcher (Task 8).
|
|
114
|
+
//
|
|
115
|
+
// No schema param: assignViaPath/defineAtPath resolve the schema from the
|
|
116
|
+
// session. `isDefault` (below) only affected a dropped console message and the
|
|
117
|
+
// /variables/ context path the raw tool never enters — so it is computed for
|
|
118
|
+
// parse parity but intentionally has no effect here.
|
|
119
|
+
const isDefault = rest.includes("--default");
|
|
120
|
+
const filtered = rest.filter((r) => r !== "--default" && !r.startsWith("--"));
|
|
121
|
+
|
|
122
|
+
const hasCursor = filtered.includes("cursor");
|
|
123
|
+
const filteredNoCursor = hasCursor ? filtered.filter((t) => t !== "cursor") : filtered;
|
|
124
|
+
|
|
125
|
+
const specs: { path: string; value: string }[] = [];
|
|
126
|
+
const deferredVarDefs: [string, string][] = [];
|
|
127
|
+
|
|
128
|
+
const hasKeyValue = filteredNoCursor.some(
|
|
129
|
+
(t) => t.includes("=") && !t.startsWith("$") && !t.startsWith("{") && !t.startsWith("["),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
let cursorFieldPrefix = "";
|
|
133
|
+
|
|
134
|
+
if (hasKeyValue) {
|
|
135
|
+
let fieldPrefix = "";
|
|
136
|
+
let startIdx = 0;
|
|
137
|
+
|
|
138
|
+
if (filteredNoCursor.length > 0 && !filteredNoCursor[0].includes("=")) {
|
|
139
|
+
fieldPrefix = filteredNoCursor[0];
|
|
140
|
+
startIdx = 1;
|
|
141
|
+
}
|
|
142
|
+
cursorFieldPrefix = fieldPrefix;
|
|
143
|
+
|
|
144
|
+
for (let i = startIdx; i < filteredNoCursor.length; i++) {
|
|
145
|
+
const token = filteredNoCursor[i];
|
|
146
|
+
const eqIdx = token.indexOf("=");
|
|
147
|
+
if (eqIdx > 0) {
|
|
148
|
+
let key = token.slice(0, eqIdx);
|
|
149
|
+
let val = token.slice(eqIdx + 1);
|
|
150
|
+
|
|
151
|
+
// orderBy shorthand: orderBy=Field:DESC → orderBy.Field.order=DESC
|
|
152
|
+
if (key === "orderBy" || key.endsWith("/orderBy") || key.endsWith(".orderBy")) {
|
|
153
|
+
const orderMatch = val.match(/^(\w+):(ASC|DESC)$/i);
|
|
154
|
+
if (orderMatch) {
|
|
155
|
+
key = `${key}.${orderMatch[1]}.order`;
|
|
156
|
+
val = orderMatch[2].toUpperCase();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// where shorthand: where.Field=Value → where={"Field":{"eq":"Value"}}
|
|
161
|
+
// Also supports where.Field.op=Value and where.Field=$var (deferred binding).
|
|
162
|
+
const whereMatch = key.match(/^(.*?)?where\.(\w+)(?:\.(\w+))?$/);
|
|
163
|
+
if (whereMatch) {
|
|
164
|
+
const prefix = whereMatch[1] || "";
|
|
165
|
+
const field = whereMatch[2];
|
|
166
|
+
const op =
|
|
167
|
+
whereMatch[3] ||
|
|
168
|
+
(val.includes("%") && !val.startsWith("$") && !val.startsWith("{") ? "like" : "eq");
|
|
169
|
+
|
|
170
|
+
if (val.startsWith("$")) {
|
|
171
|
+
let resolvedPrefix = fieldPrefix;
|
|
172
|
+
if (
|
|
173
|
+
resolvedPrefix &&
|
|
174
|
+
!resolvedPrefix.startsWith("/") &&
|
|
175
|
+
!resolvedPrefix.startsWith("query/") &&
|
|
176
|
+
!resolvedPrefix.startsWith("@args")
|
|
177
|
+
) {
|
|
178
|
+
resolvedPrefix = `query/${resolvedPrefix}`;
|
|
179
|
+
}
|
|
180
|
+
const varPath = resolvedPrefix
|
|
181
|
+
? `${resolvedPrefix}/@args/${prefix}where/${field}/${op}`
|
|
182
|
+
: `${prefix}where/${field}/${op}`;
|
|
183
|
+
deferredVarDefs.push([val, varPath]);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
key = `${prefix}where`;
|
|
188
|
+
let parsedVal: unknown = val;
|
|
189
|
+
if (val === "null") parsedVal = null;
|
|
190
|
+
else if (val === "true") parsedVal = true;
|
|
191
|
+
else if (val === "false") parsedVal = false;
|
|
192
|
+
else if (/^-?\d+(\.\d+)?$/.test(val)) parsedVal = Number(val);
|
|
193
|
+
else parsedVal = val;
|
|
194
|
+
val = JSON.stringify({ [field]: { [op]: parsedVal } });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let resolvedPrefix = fieldPrefix;
|
|
198
|
+
if (
|
|
199
|
+
resolvedPrefix &&
|
|
200
|
+
!resolvedPrefix.startsWith("/") &&
|
|
201
|
+
!resolvedPrefix.startsWith("query/") &&
|
|
202
|
+
!resolvedPrefix.startsWith("@args")
|
|
203
|
+
) {
|
|
204
|
+
resolvedPrefix = `query/${resolvedPrefix}`;
|
|
205
|
+
}
|
|
206
|
+
const fullPath = resolvedPrefix ? `${resolvedPrefix}/@args/${key}` : key;
|
|
207
|
+
specs.push({ path: fullPath, value: val });
|
|
208
|
+
} else {
|
|
209
|
+
if (i + 1 < filteredNoCursor.length) {
|
|
210
|
+
let resolvedPrefix = fieldPrefix;
|
|
211
|
+
if (
|
|
212
|
+
resolvedPrefix &&
|
|
213
|
+
!resolvedPrefix.startsWith("/") &&
|
|
214
|
+
!resolvedPrefix.startsWith("query/") &&
|
|
215
|
+
!resolvedPrefix.startsWith("@args")
|
|
216
|
+
) {
|
|
217
|
+
resolvedPrefix = `query/${resolvedPrefix}`;
|
|
218
|
+
}
|
|
219
|
+
const path = resolvedPrefix ? `${resolvedPrefix}/@args/${token}` : token;
|
|
220
|
+
specs.push({ path, value: filteredNoCursor[i + 1] });
|
|
221
|
+
i++;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} else if (filteredNoCursor.length > 0) {
|
|
226
|
+
for (let i = 0; i < filteredNoCursor.length; i += 2) {
|
|
227
|
+
const dotPath = filteredNoCursor[i];
|
|
228
|
+
const value = filteredNoCursor[i + 1];
|
|
229
|
+
if (!dotPath || value === undefined) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
"set: usage is `set [<field-path>] <key>=<value> ...` or `set <path> <value>`",
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
specs.push({ path: dotPath, value });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (specs.length === 0 && !hasCursor && deferredVarDefs.length === 0) {
|
|
239
|
+
throw new Error("set: usage is `set [<field-path>] <key>=<value> ...` or `set <path> <value>`");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for (const { path: dotPath, value } of specs) {
|
|
243
|
+
assignViaPath(session, dotPath, value);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (const [varName, varPath] of deferredVarDefs) {
|
|
247
|
+
defineAtPath(session, varName.replace(/^\$/, ""), varPath);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (hasCursor) {
|
|
251
|
+
const prefix = cursorFieldPrefix || filteredNoCursor[0] || "";
|
|
252
|
+
if (!prefix) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
"set cursor requires a field path prefix, e.g. `set uiapi/query/Case cursor`",
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const cursorPath = `${prefix}/@args/after`;
|
|
258
|
+
defineAtPath(session, "after", cursorPath);
|
|
259
|
+
try {
|
|
260
|
+
selectLeafInSession(session, `${prefix}/pageInfo/hasNextPage`);
|
|
261
|
+
selectLeafInSession(session, `${prefix}/pageInfo/endCursor`);
|
|
262
|
+
} catch {
|
|
263
|
+
// pageInfo may already be selected or path may not resolve — ignore (matches CLI).
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
void isDefault;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function applyVar(session: QuerySession, rest: string[]): void {
|
|
271
|
+
// Ported from commands/query.ts `var` case → queryDefine → defineAtPath.
|
|
272
|
+
// Raw always supplies a path (no `cd`, so no current-args-position form).
|
|
273
|
+
const varName = rest[0];
|
|
274
|
+
if (!varName) {
|
|
275
|
+
throw new Error("var requires a name, e.g. var $id <path> [default]");
|
|
276
|
+
}
|
|
277
|
+
const pathArg = rest[1];
|
|
278
|
+
if (!pathArg) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`var ${varName} requires a schema path, e.g. var ${varName} uiapi/query/Case/@args/where/Id/eq`,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
const cleanName = varName.replace(/^\$/, "");
|
|
284
|
+
const defaultValue = rest[2];
|
|
285
|
+
defineAtPath(session, cleanName, pathArg, defaultValue);
|
|
286
|
+
}
|
package/src/lib/auth.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
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 "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { Org, AuthInfo } from "@salesforce/core";
|
|
10
|
+
import { graphitiHome } from "./fs-utils.js";
|
|
11
|
+
|
|
12
|
+
export interface OrgAuth {
|
|
13
|
+
/**
|
|
14
|
+
* Captured at resolution time as a liveness check (a missing token means the
|
|
15
|
+
* auth has expired). NOT used to authenticate requests: the HTTP layer builds
|
|
16
|
+
* a `Connection` from the alias and lets `@salesforce/core` inject/refresh the
|
|
17
|
+
* token. Retained for callers that surface token state.
|
|
18
|
+
*/
|
|
19
|
+
accessToken: string;
|
|
20
|
+
instanceUrl: string;
|
|
21
|
+
username: string;
|
|
22
|
+
orgId: string;
|
|
23
|
+
alias: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface OrgListEntry {
|
|
27
|
+
alias: string;
|
|
28
|
+
username: string;
|
|
29
|
+
instanceUrl: string;
|
|
30
|
+
orgId: string;
|
|
31
|
+
type: "scratch" | "non-scratch";
|
|
32
|
+
isConnected: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// `--target-org` accepts both aliases and usernames; SF usernames are email-shaped
|
|
36
|
+
// (e.g. `dev+build1@scratch-org-12.example.com`), so the charset must include
|
|
37
|
+
// `@`, `.`, `+`. Leading hyphen and leading dot are still forbidden to defend
|
|
38
|
+
// against `--target-org -foo` flag injection and path-relative parsing quirks.
|
|
39
|
+
// Length cap follows RFC 5321 (253) since SF usernames can be long.
|
|
40
|
+
const ORG_ALIAS_RE = /^[A-Za-z0-9_][A-Za-z0-9_.+@-]{0,252}$/;
|
|
41
|
+
|
|
42
|
+
function assertValidOrgAlias(orgAlias: string): void {
|
|
43
|
+
if (typeof orgAlias !== "string" || !ORG_ALIAS_RE.test(orgAlias)) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"Invalid org alias or username. Must be 1-253 characters, start with [A-Za-z0-9_], and contain only [A-Za-z0-9_.+@-].",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const authCache = new Map<string, OrgAuth>();
|
|
51
|
+
|
|
52
|
+
export function clearOrgAuthCache(): void {
|
|
53
|
+
authCache.clear();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Reads Salesforce org credentials via @salesforce/core.
|
|
58
|
+
* Results are memoized for the lifetime of the process so repeated calls
|
|
59
|
+
* within the same command (or an interactive session) only resolve once.
|
|
60
|
+
*/
|
|
61
|
+
export async function getOrgAuth(orgAlias: string): Promise<OrgAuth> {
|
|
62
|
+
assertValidOrgAlias(orgAlias);
|
|
63
|
+
|
|
64
|
+
const cached = authCache.get(orgAlias);
|
|
65
|
+
if (cached) return cached;
|
|
66
|
+
|
|
67
|
+
let connection;
|
|
68
|
+
try {
|
|
69
|
+
const org = await Org.create({ aliasOrUsername: orgAlias });
|
|
70
|
+
connection = org.getConnection();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Failed to get org info for "${orgAlias}". Is the alias correct? Have you run \`sf org login web --alias ${orgAlias}\`?\n${msg}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const fields = connection.getAuthInfo().getFields();
|
|
79
|
+
if (!connection.accessToken || !connection.instanceUrl) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Missing accessToken or instanceUrl for "${orgAlias}". Token may have expired -- try \`sf org login web --alias ${orgAlias}\` to re-authenticate.`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const auth: OrgAuth = {
|
|
86
|
+
accessToken: connection.accessToken,
|
|
87
|
+
instanceUrl: connection.instanceUrl.replace(/\/+$/, ""),
|
|
88
|
+
username: fields.username ?? "unknown",
|
|
89
|
+
orgId: fields.orgId ?? "unknown",
|
|
90
|
+
alias: orgAlias,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
authCache.set(orgAlias, auth);
|
|
94
|
+
return auth;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getConnectedOrgAliases(): Set<string> {
|
|
98
|
+
const schemasDir = path.join(graphitiHome(), "schemas");
|
|
99
|
+
const sessionsDir = path.join(graphitiHome(), "sessions");
|
|
100
|
+
const aliases = new Set<string>();
|
|
101
|
+
|
|
102
|
+
if (fs.existsSync(sessionsDir)) {
|
|
103
|
+
for (const file of fs.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"))) {
|
|
104
|
+
try {
|
|
105
|
+
const raw = JSON.parse(fs.readFileSync(path.join(sessionsDir, file), "utf-8"));
|
|
106
|
+
if (raw.orgAlias) aliases.add(raw.orgAlias);
|
|
107
|
+
} catch {
|
|
108
|
+
/* skip corrupt sessions */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (fs.existsSync(schemasDir)) {
|
|
114
|
+
if (fs.readdirSync(schemasDir).some((f) => f.endsWith(".json"))) {
|
|
115
|
+
aliases.add("__has_schemas__");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return aliases;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Lists available Salesforce orgs via AuthInfo.listAllAuthorizations().
|
|
124
|
+
* Marks orgs that have a cached graphiti schema or session.
|
|
125
|
+
*/
|
|
126
|
+
export async function listOrgs(): Promise<OrgListEntry[]> {
|
|
127
|
+
let auths;
|
|
128
|
+
try {
|
|
129
|
+
auths = await AuthInfo.listAllAuthorizations();
|
|
130
|
+
} catch (err) {
|
|
131
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
132
|
+
throw new Error(`Failed to list orgs: ${msg}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const connected = getConnectedOrgAliases();
|
|
136
|
+
const entries: OrgListEntry[] = [];
|
|
137
|
+
|
|
138
|
+
for (const a of auths) {
|
|
139
|
+
const aliases = a.aliases ?? [];
|
|
140
|
+
const displayId = aliases[0] ?? a.username;
|
|
141
|
+
if (!displayId) continue; // neither alias nor username — skip
|
|
142
|
+
|
|
143
|
+
const isConnected =
|
|
144
|
+
aliases.some((al) => connected.has(al)) || (!!a.username && connected.has(a.username));
|
|
145
|
+
|
|
146
|
+
entries.push({
|
|
147
|
+
alias: displayId,
|
|
148
|
+
username: a.username ?? "unknown",
|
|
149
|
+
instanceUrl: (a.instanceUrl ?? "").replace(/\/+$/, ""),
|
|
150
|
+
orgId: a.orgId ?? "",
|
|
151
|
+
type: a.isScratchOrg ? "scratch" : "non-scratch",
|
|
152
|
+
isConnected,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return entries;
|
|
157
|
+
}
|