@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,670 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* For full license text, see the LICENSE.txt file
|
|
5
|
+
*/
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any -- graphiti traverses untyped schema/introspection JSON; see follow-up to replace with `unknown` + narrowing */
|
|
7
|
+
|
|
8
|
+
import type { GraphQLSchema } from "graphql";
|
|
9
|
+
import {
|
|
10
|
+
CommandError,
|
|
11
|
+
EXIT_CODES,
|
|
12
|
+
getSessionSchema,
|
|
13
|
+
formatAliasContext,
|
|
14
|
+
printQuery,
|
|
15
|
+
detectSObjectName,
|
|
16
|
+
} from "./query-helpers.js";
|
|
17
|
+
import { getOrgAuth } from "../lib/auth.js";
|
|
18
|
+
import {
|
|
19
|
+
generateTypes,
|
|
20
|
+
collectSessionSObjects,
|
|
21
|
+
normalizeCodegenLanguage,
|
|
22
|
+
SUPPORTED_CODEGEN_LANGUAGES,
|
|
23
|
+
} from "../lib/codegen.js";
|
|
24
|
+
import { formatNavigationPath, formatValidationErrors } from "../lib/formatter.js";
|
|
25
|
+
import { DEFAULT_API_VERSION, executeGraphQL } from "../lib/introspect.js";
|
|
26
|
+
import {
|
|
27
|
+
getCachedObjectInfo,
|
|
28
|
+
getObjectInfo,
|
|
29
|
+
getRequiredCreateFields,
|
|
30
|
+
type FieldMetadata,
|
|
31
|
+
type ObjectInfoResult,
|
|
32
|
+
} from "../lib/object-info.js";
|
|
33
|
+
import { renderQuery } from "../lib/query-builder.js";
|
|
34
|
+
import {
|
|
35
|
+
loadSession,
|
|
36
|
+
listSessions,
|
|
37
|
+
buildRuntimeVariables,
|
|
38
|
+
getNavigationContext,
|
|
39
|
+
isInArgsContext,
|
|
40
|
+
queryNavToSchemaPath,
|
|
41
|
+
getChildren,
|
|
42
|
+
findVariableReferences,
|
|
43
|
+
formatPath,
|
|
44
|
+
type FieldProjectionNode,
|
|
45
|
+
type QuerySession,
|
|
46
|
+
} from "../lib/session.js";
|
|
47
|
+
import { validateQuery } from "../lib/validator.js";
|
|
48
|
+
import { resolvePath } from "../lib/walker.js";
|
|
49
|
+
|
|
50
|
+
export function queryShowJson(sessionId: string): void {
|
|
51
|
+
const session = loadSession(sessionId);
|
|
52
|
+
const queryString = renderQuery(session);
|
|
53
|
+
const variables = buildRuntimeVariables(session);
|
|
54
|
+
|
|
55
|
+
const result: Record<string, unknown> = {
|
|
56
|
+
command: "show",
|
|
57
|
+
session: {
|
|
58
|
+
id: session.id,
|
|
59
|
+
name: session.name ?? null,
|
|
60
|
+
orgAlias: session.orgAlias,
|
|
61
|
+
instanceUrl: session.instanceUrl ?? null,
|
|
62
|
+
operation: session.operation,
|
|
63
|
+
navigationPath: formatPath(session.navigationPath),
|
|
64
|
+
},
|
|
65
|
+
query: queryString,
|
|
66
|
+
variables: session.variables.map((v) => ({
|
|
67
|
+
name: v.name,
|
|
68
|
+
type: v.type,
|
|
69
|
+
defaultValue: v.defaultValue ?? null,
|
|
70
|
+
runtimeValue: v.runtimeValue ?? null,
|
|
71
|
+
references: findVariableReferences(session, v.name).map((ref) => ({
|
|
72
|
+
fieldPath: formatPath(ref.fieldPath),
|
|
73
|
+
argPath: ref.subPath.length > 0 ? `${ref.argName}.${ref.subPath.join(".")}` : ref.argName,
|
|
74
|
+
})),
|
|
75
|
+
})),
|
|
76
|
+
resolvedVariables: Object.keys(variables).length > 0 ? variables : null,
|
|
77
|
+
selectedFields: session.nodes.filter(
|
|
78
|
+
(n) => n.kind === "field" && getChildren(session, n.id).length === 0,
|
|
79
|
+
).length,
|
|
80
|
+
totalNodes: session.nodes.length,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
console.log(JSON.stringify(result, null, 2));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function queryShowQueryOnly(sessionId: string): void {
|
|
87
|
+
const session = loadSession(sessionId);
|
|
88
|
+
const queryString = renderQuery(session);
|
|
89
|
+
console.log(queryString);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function queryShow(sessionId: string): void {
|
|
93
|
+
const session = loadSession(sessionId);
|
|
94
|
+
|
|
95
|
+
console.log("Session:");
|
|
96
|
+
console.log(` ID: ${session.id}${session.name ? ` (${session.name})` : ""}`);
|
|
97
|
+
console.log(` Org: ${session.orgAlias}`);
|
|
98
|
+
console.log(` Operation: ${session.operation}`);
|
|
99
|
+
if (session.instanceUrl) console.log(` Instance: ${session.instanceUrl}`);
|
|
100
|
+
console.log("");
|
|
101
|
+
|
|
102
|
+
console.log(formatNavigationPath(session.navigationPath));
|
|
103
|
+
|
|
104
|
+
const aliasCtx = formatAliasContext(session);
|
|
105
|
+
if (aliasCtx) console.log(aliasCtx);
|
|
106
|
+
|
|
107
|
+
const ctx = getNavigationContext(session.navigationPath);
|
|
108
|
+
if (ctx === "query" && !isInArgsContext(session.navigationPath)) {
|
|
109
|
+
const schemaPath = queryNavToSchemaPath(session.navigationPath);
|
|
110
|
+
if (schemaPath.length > 0) {
|
|
111
|
+
try {
|
|
112
|
+
const statusSchema = getSessionSchema(session);
|
|
113
|
+
const wr = resolvePath(statusSchema, session.operation, schemaPath);
|
|
114
|
+
console.log(`Type: ${wr.typeName} (${wr.kind})`);
|
|
115
|
+
} catch {
|
|
116
|
+
/* path may be stale */
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log("");
|
|
122
|
+
printQuery(session);
|
|
123
|
+
|
|
124
|
+
if (session.variables.length > 0) {
|
|
125
|
+
console.log("");
|
|
126
|
+
console.log("Variables:");
|
|
127
|
+
for (const v of session.variables) {
|
|
128
|
+
let line = ` $${v.name}: ${v.type}`;
|
|
129
|
+
if (v.defaultValue !== undefined) line += ` (default: ${v.defaultValue})`;
|
|
130
|
+
if (v.runtimeValue !== undefined) line += ` = ${v.runtimeValue}`;
|
|
131
|
+
console.log(line);
|
|
132
|
+
|
|
133
|
+
const refs = findVariableReferences(session, v.name);
|
|
134
|
+
for (const ref of refs) {
|
|
135
|
+
const fieldDesc = formatPath(ref.fieldPath);
|
|
136
|
+
const argPath =
|
|
137
|
+
ref.subPath.length > 0 ? `${ref.argName}.${ref.subPath.join(".")}` : ref.argName;
|
|
138
|
+
console.log(` → ${fieldDesc} @args/${argPath}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const resolved = buildRuntimeVariables(session);
|
|
143
|
+
if (Object.keys(resolved).length > 0) {
|
|
144
|
+
console.log("");
|
|
145
|
+
console.log("Resolved variable values (sent at execution):");
|
|
146
|
+
console.log(JSON.stringify(resolved, null, 2));
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
console.log("");
|
|
150
|
+
console.log("Variables: (none)");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function queryValidate(
|
|
155
|
+
sessionId: string,
|
|
156
|
+
_strict = false,
|
|
157
|
+
opts?: { codegen?: boolean },
|
|
158
|
+
): Promise<void> {
|
|
159
|
+
const session = loadSession(sessionId);
|
|
160
|
+
const schema = getSessionSchema(session);
|
|
161
|
+
const queryString = renderQuery(session);
|
|
162
|
+
|
|
163
|
+
console.log("Validating query...");
|
|
164
|
+
console.log("");
|
|
165
|
+
console.log(queryString);
|
|
166
|
+
console.log("");
|
|
167
|
+
|
|
168
|
+
const errors = validateQuery(schema, queryString);
|
|
169
|
+
console.log(formatValidationErrors(errors));
|
|
170
|
+
|
|
171
|
+
if (errors.length > 0)
|
|
172
|
+
throw new CommandError(
|
|
173
|
+
"Query has validation errors (see above).",
|
|
174
|
+
EXIT_CODES.VALIDATION_FAILURE,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Salesforce-semantic validation
|
|
178
|
+
const warnings = runSemanticValidation(session, schema);
|
|
179
|
+
if (warnings.length > 0) {
|
|
180
|
+
console.log("Salesforce semantic warnings:");
|
|
181
|
+
for (const w of warnings) {
|
|
182
|
+
console.log(` ⚠ ${w}`);
|
|
183
|
+
}
|
|
184
|
+
console.log("");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (session.operation === "mutation") {
|
|
188
|
+
await validateStrictMutation(session);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// --codegen flag: generate types after successful validation
|
|
192
|
+
if (opts?.codegen) {
|
|
193
|
+
// Pre-warm ObjectInfo cache for picklist enrichment
|
|
194
|
+
const sObjects = collectSessionSObjects(session, schema);
|
|
195
|
+
if (sObjects.size > 0) {
|
|
196
|
+
try {
|
|
197
|
+
const auth = await getOrgAuth(session.orgAlias);
|
|
198
|
+
await Promise.all(
|
|
199
|
+
[...sObjects].map((name) =>
|
|
200
|
+
getObjectInfo(auth, session.orgAlias, name).catch(() => {
|
|
201
|
+
/* non-fatal: ObjectInfo warm-up is best-effort */
|
|
202
|
+
}),
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
} catch {
|
|
206
|
+
/* non-critical */
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
console.log("");
|
|
210
|
+
const code = generateTypes(session, schema);
|
|
211
|
+
console.log(code);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function queryValidateAll(): void {
|
|
216
|
+
const sessions = listSessions().sort((a, b) => (a.name ?? a.id).localeCompare(b.name ?? b.id));
|
|
217
|
+
|
|
218
|
+
if (sessions.length === 0) {
|
|
219
|
+
console.log("No sessions found.");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let passed = 0;
|
|
224
|
+
let failed = 0;
|
|
225
|
+
const failures: { name: string; errors: string[] }[] = [];
|
|
226
|
+
|
|
227
|
+
for (const sessionMeta of sessions) {
|
|
228
|
+
const identifier = sessionMeta.name ?? sessionMeta.id;
|
|
229
|
+
try {
|
|
230
|
+
const session = loadSession(sessionMeta.id);
|
|
231
|
+
const schema = getSessionSchema(session);
|
|
232
|
+
const queryString = renderQuery(session);
|
|
233
|
+
const errors = validateQuery(schema, queryString);
|
|
234
|
+
if (errors.length === 0) {
|
|
235
|
+
console.log(` ✓ ${identifier}`);
|
|
236
|
+
passed++;
|
|
237
|
+
} else {
|
|
238
|
+
const errorMsgs = errors.map((e) =>
|
|
239
|
+
typeof e === "string" ? e : ((e as any).message ?? String(e)),
|
|
240
|
+
);
|
|
241
|
+
console.log(` ✗ ${identifier} (${errors.length} error${errors.length !== 1 ? "s" : ""})`);
|
|
242
|
+
failures.push({ name: identifier, errors: errorMsgs });
|
|
243
|
+
failed++;
|
|
244
|
+
}
|
|
245
|
+
} catch (err: any) {
|
|
246
|
+
console.log(` ✗ ${identifier} — ${err.message}`);
|
|
247
|
+
failures.push({ name: identifier, errors: [err.message] });
|
|
248
|
+
failed++;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log("");
|
|
253
|
+
console.log(`${passed + failed} sessions: ${passed} valid, ${failed} failed`);
|
|
254
|
+
|
|
255
|
+
if (failures.length > 0) {
|
|
256
|
+
console.log("");
|
|
257
|
+
for (const f of failures) {
|
|
258
|
+
console.log(`${f.name}:`);
|
|
259
|
+
for (const e of f.errors) {
|
|
260
|
+
console.log(` - ${e}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
throw new CommandError(
|
|
264
|
+
`${failed} session${failed !== 1 ? "s" : ""} failed validation.`,
|
|
265
|
+
EXIT_CODES.VALIDATION_FAILURE,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Performs Salesforce-specific semantic validation beyond GraphQL schema checks:
|
|
272
|
+
* - Hint when `first` is not set on connection fields (optional but recommended)
|
|
273
|
+
* - Non-filterable fields used in `where` clauses
|
|
274
|
+
* - Non-sortable fields used in `orderBy` clauses
|
|
275
|
+
* - Empty selection sets on connection types
|
|
276
|
+
*/
|
|
277
|
+
function runSemanticValidation(session: QuerySession, schema: GraphQLSchema): string[] {
|
|
278
|
+
const warnings: string[] = [];
|
|
279
|
+
|
|
280
|
+
for (const node of session.nodes) {
|
|
281
|
+
if (node.kind !== "field") continue;
|
|
282
|
+
const fieldNode = node as FieldProjectionNode;
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const wr = resolvePath(schema, session.operation, fieldNode.schemaPath);
|
|
286
|
+
const isConnection =
|
|
287
|
+
wr.typeName.endsWith("Connection") ||
|
|
288
|
+
(wr.fields.some((f) => f.name === "edges") && wr.fields.some((f) => f.name === "pageInfo"));
|
|
289
|
+
|
|
290
|
+
// Hint (not error): `first` is optional but recommended for predictable pagination
|
|
291
|
+
if (isConnection && !fieldNode.args["first"]) {
|
|
292
|
+
const pathStr = formatPath(fieldNode.schemaPath);
|
|
293
|
+
warnings.push(
|
|
294
|
+
`${pathStr}: no \`first\` argument set. Consider adding \`first\` to control page size.`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (isConnection) {
|
|
299
|
+
const children = getChildren(session, fieldNode.id);
|
|
300
|
+
if (children.length === 0) {
|
|
301
|
+
const pathStr = formatPath(fieldNode.schemaPath);
|
|
302
|
+
warnings.push(
|
|
303
|
+
`${pathStr}: connection has no selected fields. Add selections under edges/node/.`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Warn when `after` arg is bound (pagination) but pageInfo is not selected
|
|
308
|
+
if (fieldNode.args["after"]) {
|
|
309
|
+
const hasPageInfo = children.some(
|
|
310
|
+
(c) => c.kind === "field" && (c as FieldProjectionNode).fieldName === "pageInfo",
|
|
311
|
+
);
|
|
312
|
+
if (!hasPageInfo) {
|
|
313
|
+
const pathStr = formatPath(fieldNode.schemaPath);
|
|
314
|
+
const prefix = pathStr.replace(/^\//, "");
|
|
315
|
+
warnings.push(
|
|
316
|
+
`${pathStr}: \`after\` is bound but \`pageInfo\` is not selected. Add \`select ${prefix}/pageInfo/hasNextPage ${prefix}/pageInfo/endCursor\` for pagination.`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} catch {
|
|
322
|
+
/* path may not resolve, skip */
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Check non-filterable where and non-sortable orderBy
|
|
326
|
+
if (fieldNode.args["where"] || fieldNode.args["orderBy"]) {
|
|
327
|
+
const sObjectName = detectSObjectName(session, fieldNode.schemaPath);
|
|
328
|
+
if (sObjectName) {
|
|
329
|
+
const objInfo = getCachedObjectInfo(session.orgAlias, sObjectName);
|
|
330
|
+
if (objInfo) {
|
|
331
|
+
const metaByApi = new Map(objInfo.fields.map((f) => [f.apiName, f]));
|
|
332
|
+
const pathStr = formatPath(fieldNode.schemaPath);
|
|
333
|
+
|
|
334
|
+
if (fieldNode.args["where"]) {
|
|
335
|
+
try {
|
|
336
|
+
const whereVal = JSON.parse(fieldNode.args["where"]);
|
|
337
|
+
checkFilterableFields(whereVal, metaByApi, pathStr, warnings);
|
|
338
|
+
} catch {
|
|
339
|
+
/* not JSON, skip */
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (fieldNode.args["orderBy"]) {
|
|
344
|
+
try {
|
|
345
|
+
const orderVal = JSON.parse(fieldNode.args["orderBy"]);
|
|
346
|
+
const items = Array.isArray(orderVal) ? orderVal : [orderVal];
|
|
347
|
+
for (const item of items) {
|
|
348
|
+
if (typeof item === "object" && item !== null) {
|
|
349
|
+
for (const fn of Object.keys(item)) {
|
|
350
|
+
const meta = metaByApi.get(fn);
|
|
351
|
+
if (meta && !meta.sortable) {
|
|
352
|
+
warnings.push(
|
|
353
|
+
`${pathStr}: orderBy uses ${fn} which is not sortable per ObjectInfo.`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} catch {
|
|
360
|
+
/* not JSON, skip */
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return warnings;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function checkFilterableFields(
|
|
372
|
+
obj: unknown,
|
|
373
|
+
metaByApi: Map<string, { filterable: boolean }>,
|
|
374
|
+
pathStr: string,
|
|
375
|
+
warnings: string[],
|
|
376
|
+
): void {
|
|
377
|
+
if (typeof obj !== "object" || obj === null) return;
|
|
378
|
+
for (const [key, val] of Object.entries(obj as Record<string, unknown>)) {
|
|
379
|
+
if (key === "and" || key === "or") {
|
|
380
|
+
const items = Array.isArray(val) ? val : [val];
|
|
381
|
+
for (const item of items) checkFilterableFields(item, metaByApi, pathStr, warnings);
|
|
382
|
+
} else if (key === "not") {
|
|
383
|
+
checkFilterableFields(val, metaByApi, pathStr, warnings);
|
|
384
|
+
} else {
|
|
385
|
+
const meta = metaByApi.get(key);
|
|
386
|
+
if (meta && !meta.filterable) {
|
|
387
|
+
warnings.push(
|
|
388
|
+
`${pathStr}: where clause uses ${key} which is not filterable per ObjectInfo.`,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function buildSampleInputFields(
|
|
396
|
+
requiredFields: FieldMetadata[],
|
|
397
|
+
_sObjectName: string,
|
|
398
|
+
info: ObjectInfoResult,
|
|
399
|
+
): Record<string, string> {
|
|
400
|
+
const sampleFields: Record<string, string> = {};
|
|
401
|
+
for (const f of requiredFields) {
|
|
402
|
+
const picklist = info.picklists.find((p) => p.apiName === f.apiName);
|
|
403
|
+
if (picklist && picklist.values.length > 0) {
|
|
404
|
+
sampleFields[f.apiName] = `"${picklist.values[0].value}"`;
|
|
405
|
+
} else if (f.dataType === "STRING" || f.dataType === "TEXTAREA") {
|
|
406
|
+
sampleFields[f.apiName] = `"<${f.label ?? f.apiName}>"`;
|
|
407
|
+
} else if (
|
|
408
|
+
f.dataType === "CURRENCY" ||
|
|
409
|
+
f.dataType === "DOUBLE" ||
|
|
410
|
+
f.dataType === "INT" ||
|
|
411
|
+
f.dataType === "PERCENT"
|
|
412
|
+
) {
|
|
413
|
+
sampleFields[f.apiName] = "0";
|
|
414
|
+
} else if (f.dataType === "DATE") {
|
|
415
|
+
sampleFields[f.apiName] = `"${new Date().toISOString().split("T")[0]}"`;
|
|
416
|
+
} else if (f.dataType === "DATETIME") {
|
|
417
|
+
sampleFields[f.apiName] = `"${new Date().toISOString()}"`;
|
|
418
|
+
} else if (f.dataType === "BOOLEAN") {
|
|
419
|
+
sampleFields[f.apiName] = "false";
|
|
420
|
+
} else if (f.dataType === "REFERENCE") {
|
|
421
|
+
sampleFields[f.apiName] = '"<record-id>"';
|
|
422
|
+
} else {
|
|
423
|
+
sampleFields[f.apiName] = `"<${f.apiName}>"`;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return sampleFields;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export async function validateStrictMutation(session: QuerySession): Promise<void> {
|
|
430
|
+
const rootChildren = getChildren(session, null);
|
|
431
|
+
if (rootChildren.length === 0) return;
|
|
432
|
+
|
|
433
|
+
for (const child of rootChildren) {
|
|
434
|
+
if (child.kind !== "field") continue;
|
|
435
|
+
const grandChildren = getChildren(session, child.id);
|
|
436
|
+
for (const gc of grandChildren) {
|
|
437
|
+
if (gc.kind !== "field") continue;
|
|
438
|
+
const match = gc.fieldName.match(/^(\w+)(Create|Update)$/);
|
|
439
|
+
if (!match) continue;
|
|
440
|
+
|
|
441
|
+
const sObjectName = match[1];
|
|
442
|
+
const mutationType = match[2];
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
let auth;
|
|
446
|
+
try {
|
|
447
|
+
auth = await getOrgAuth(session.orgAlias);
|
|
448
|
+
} catch (authErr: any) {
|
|
449
|
+
throw new CommandError(
|
|
450
|
+
`Authentication failed for "${session.orgAlias}": ${authErr.message}`,
|
|
451
|
+
EXIT_CODES.AUTH_FAILURE,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const info = await getObjectInfo(auth, session.orgAlias, sObjectName);
|
|
455
|
+
const requiredFields = getRequiredCreateFields(info);
|
|
456
|
+
|
|
457
|
+
if (requiredFields.length === 0) continue;
|
|
458
|
+
|
|
459
|
+
const inputVar = session.variables.find((v) => {
|
|
460
|
+
const typeLower = v.type.toLowerCase();
|
|
461
|
+
return (
|
|
462
|
+
typeLower.includes(sObjectName.toLowerCase()) &&
|
|
463
|
+
typeLower.includes(mutationType.toLowerCase())
|
|
464
|
+
);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
if (!inputVar?.runtimeValue) {
|
|
468
|
+
const sampleFields = buildSampleInputFields(requiredFields, sObjectName, info);
|
|
469
|
+
const cleaned: Record<string, unknown> = {};
|
|
470
|
+
for (const [k, v] of Object.entries(sampleFields)) {
|
|
471
|
+
try {
|
|
472
|
+
cleaned[k] = JSON.parse(v);
|
|
473
|
+
} catch {
|
|
474
|
+
cleaned[k] = v;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const sampleJson = JSON.stringify({ [sObjectName]: cleaned }, null, 2);
|
|
478
|
+
const varName = inputVar?.name ?? "input";
|
|
479
|
+
console.log("");
|
|
480
|
+
console.log(`Strict validation for ${sObjectName}${mutationType}:`);
|
|
481
|
+
console.log(
|
|
482
|
+
` Required fields (must provide): ${requiredFields.map((f) => f.apiName).join(", ")}`,
|
|
483
|
+
);
|
|
484
|
+
console.log(` No runtime variable value set — cannot verify field coverage.`);
|
|
485
|
+
console.log("");
|
|
486
|
+
console.log(` Fillable template — set this with:`);
|
|
487
|
+
console.log(` vars value ${varName} '${sampleJson}'`);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
const parsed = JSON.parse(inputVar.runtimeValue);
|
|
493
|
+
const innerData = parsed[sObjectName] ?? parsed;
|
|
494
|
+
const providedFields = Object.keys(innerData);
|
|
495
|
+
const missing = requiredFields.filter((f) => !providedFields.includes(f.apiName));
|
|
496
|
+
|
|
497
|
+
console.log("");
|
|
498
|
+
console.log(`Strict validation for ${sObjectName}${mutationType}:`);
|
|
499
|
+
if (missing.length === 0) {
|
|
500
|
+
console.log(` All required fields provided.`);
|
|
501
|
+
} else {
|
|
502
|
+
console.log(
|
|
503
|
+
` Missing required fields: ${missing.map((f) => `${f.apiName} (${f.label ?? f.apiName})`).join(", ")}`,
|
|
504
|
+
);
|
|
505
|
+
const fieldDetails = missing.map((f) => {
|
|
506
|
+
let detail = ` ${f.apiName}`;
|
|
507
|
+
if (f.label && f.label !== f.apiName) detail += ` — "${f.label}"`;
|
|
508
|
+
detail += ` (${f.dataType})`;
|
|
509
|
+
return detail;
|
|
510
|
+
});
|
|
511
|
+
console.log(" Details:");
|
|
512
|
+
fieldDetails.forEach((d) => console.log(d));
|
|
513
|
+
throw new CommandError(
|
|
514
|
+
`Strict validation failed: ${missing.length} required field(s) missing.`,
|
|
515
|
+
EXIT_CODES.VALIDATION_FAILURE,
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
} catch (e) {
|
|
519
|
+
if (e instanceof CommandError) throw e;
|
|
520
|
+
}
|
|
521
|
+
} catch (e) {
|
|
522
|
+
if (e instanceof CommandError) throw e;
|
|
523
|
+
console.log(` (Could not fetch objectInfos for ${sObjectName}: ${(e as Error).message})`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export async function queryExecute(
|
|
530
|
+
sessionId: string,
|
|
531
|
+
overrides?: Record<string, string>,
|
|
532
|
+
dryRun?: boolean,
|
|
533
|
+
): Promise<void> {
|
|
534
|
+
const session = loadSession(sessionId);
|
|
535
|
+
const schema = getSessionSchema(session);
|
|
536
|
+
const queryString = renderQuery(session);
|
|
537
|
+
const errors = validateQuery(schema, queryString);
|
|
538
|
+
if (errors.length > 0) {
|
|
539
|
+
throw new CommandError(
|
|
540
|
+
`Query has validation errors:\n${formatValidationErrors(errors)}`,
|
|
541
|
+
EXIT_CODES.VALIDATION_FAILURE,
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const variables = buildRuntimeVariables(session, overrides);
|
|
546
|
+
|
|
547
|
+
if (dryRun) {
|
|
548
|
+
console.log("--- Dry Run ---");
|
|
549
|
+
console.log("");
|
|
550
|
+
console.log("Operation: " + session.operation);
|
|
551
|
+
console.log("");
|
|
552
|
+
console.log("Query:");
|
|
553
|
+
console.log(queryString);
|
|
554
|
+
if (Object.keys(variables).length > 0) {
|
|
555
|
+
console.log("");
|
|
556
|
+
console.log("Variables:");
|
|
557
|
+
console.log(JSON.stringify(variables, null, 2));
|
|
558
|
+
}
|
|
559
|
+
console.log("");
|
|
560
|
+
console.log("Validation: passed");
|
|
561
|
+
console.log(`Org: ${session.orgAlias}`);
|
|
562
|
+
if (session.instanceUrl) {
|
|
563
|
+
console.log(`Instance: ${session.instanceUrl}`);
|
|
564
|
+
console.log(`Endpoint: ${session.instanceUrl}/services/data/v${DEFAULT_API_VERSION}/graphql`);
|
|
565
|
+
}
|
|
566
|
+
if (session.operation === "mutation") {
|
|
567
|
+
console.log("");
|
|
568
|
+
console.log(
|
|
569
|
+
"WARNING: This is a MUTATION — it will modify data when executed without --dry-run.",
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let auth;
|
|
576
|
+
try {
|
|
577
|
+
auth = await getOrgAuth(session.orgAlias);
|
|
578
|
+
} catch (e: any) {
|
|
579
|
+
throw new CommandError(
|
|
580
|
+
`Authentication failed for "${session.orgAlias}": ${e.message}`,
|
|
581
|
+
EXIT_CODES.AUTH_FAILURE,
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let result;
|
|
586
|
+
try {
|
|
587
|
+
result = await executeGraphQL(
|
|
588
|
+
auth,
|
|
589
|
+
queryString,
|
|
590
|
+
Object.keys(variables).length > 0 ? variables : undefined,
|
|
591
|
+
);
|
|
592
|
+
} catch (e: any) {
|
|
593
|
+
throw new CommandError(`Execution failed: ${e.message}`, EXIT_CODES.EXECUTION_FAILURE);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (result && typeof result === "object" && "errors" in result) {
|
|
597
|
+
const errors = (result as { errors?: { message: string }[] }).errors;
|
|
598
|
+
if (errors && errors.length > 0) {
|
|
599
|
+
console.log(JSON.stringify(result, null, 2));
|
|
600
|
+
throw new CommandError(
|
|
601
|
+
`Server returned ${errors.length} error(s): ${errors[0].message}`,
|
|
602
|
+
EXIT_CODES.EXECUTION_FAILURE,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
console.log(JSON.stringify(result, null, 2));
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export async function queryCodegen(sessionId: string, rest: string[]): Promise<void> {
|
|
611
|
+
const session = loadSession(sessionId);
|
|
612
|
+
const schema = getSessionSchema(session);
|
|
613
|
+
|
|
614
|
+
// --check flag: validate before generating types
|
|
615
|
+
if (rest.includes("--check")) {
|
|
616
|
+
const queryString = renderQuery(session);
|
|
617
|
+
const errors = validateQuery(schema, queryString);
|
|
618
|
+
if (errors.length > 0) {
|
|
619
|
+
console.log(formatValidationErrors(errors));
|
|
620
|
+
throw new CommandError(
|
|
621
|
+
"Query has validation errors (see above). Fix them before generating types.",
|
|
622
|
+
EXIT_CODES.VALIDATION_FAILURE,
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
console.log("Validation passed.");
|
|
626
|
+
console.log("");
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Pre-warm ObjectInfo cache for all SObjects referenced in the session.
|
|
630
|
+
// This ensures picklist enrichment works even when the disk cache has expired.
|
|
631
|
+
const sObjects = collectSessionSObjects(session, schema);
|
|
632
|
+
if (sObjects.size > 0) {
|
|
633
|
+
try {
|
|
634
|
+
const auth = await getOrgAuth(session.orgAlias);
|
|
635
|
+
await Promise.all(
|
|
636
|
+
[...sObjects].map((name) =>
|
|
637
|
+
getObjectInfo(auth, session.orgAlias, name).catch(() => {
|
|
638
|
+
/* non-fatal: ObjectInfo warm-up is best-effort */
|
|
639
|
+
}),
|
|
640
|
+
),
|
|
641
|
+
);
|
|
642
|
+
} catch {
|
|
643
|
+
/* auth failure is non-critical for codegen */
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const nameFlag = rest.find((_, i) => rest[i - 1] === "--name") ?? undefined;
|
|
648
|
+
const outFlag = rest.find((_, i) => rest[i - 1] === "--out") ?? undefined;
|
|
649
|
+
const languageFlag =
|
|
650
|
+
rest.find(
|
|
651
|
+
(_, i) => rest[i - 1] === "--language" || rest[i - 1] === "--lang" || rest[i - 1] === "-l",
|
|
652
|
+
) ?? undefined;
|
|
653
|
+
|
|
654
|
+
const language = languageFlag ? normalizeCodegenLanguage(languageFlag) : "typescript";
|
|
655
|
+
if (!language) {
|
|
656
|
+
throw new CommandError(
|
|
657
|
+
`Unsupported codegen language: "${languageFlag}". Supported languages: ${SUPPORTED_CODEGEN_LANGUAGES.join(", ")}.`,
|
|
658
|
+
EXIT_CODES.USER_ERROR,
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const code = generateTypes(session, schema, { typeName: nameFlag, language });
|
|
663
|
+
if (outFlag) {
|
|
664
|
+
const fs = await import("fs");
|
|
665
|
+
fs.writeFileSync(outFlag, code, "utf-8");
|
|
666
|
+
console.log(`Types written to ${outFlag}`);
|
|
667
|
+
} else {
|
|
668
|
+
console.log(code);
|
|
669
|
+
}
|
|
670
|
+
}
|