@cerios/salesforce-sobjectly 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@ A type-safe Salesforce REST API client for TypeScript. It generates types for yo
9
9
 
10
10
  ## ✨ Key Features
11
11
 
12
- - **Generated sObject types**: `sobjectly generate` reads describe metadata and writes a read type, a create input and an update input for every sObject. Required fields, restricted picklists and relationships all come from your org.
12
+ - **Generated sObject types**: `sobjectly generate` reads describe metadata and writes a read type, a create input and an update input for every sObject. Required fields, restricted picklists and relationships all come from your org. Picklists can also become named constants or enums, e.g. `CaseStatus.Working`.
13
13
  - **Typed SOQL builder**: field names are checked and autocompleted. The query result only contains the fields you selected, so `record.Phone` is a compile error unless you selected `Phone`.
14
14
  - **Relationship queries**: `selectRelated("Account", "Name")` and `selectChild("Contacts", c => c.select("Email"))` produce typed nested results.
15
15
  - **The whole REST API**: sObject CRUD, upsert by external id, query and queryAll with pagination, SOSL, Composite, Composite Graph, sObject Tree, sObject Collections, invocable actions and Flows, the Tooling API, Apex REST and Bulk API 2.0. Anything else is reachable through `request<T>()`.
package/dist/cli.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- const require_sf_cli = require("./sf-cli-DEVZQTR7.cjs");
3
- const require_codegen = require("./codegen-BgDgU2RR.cjs");
2
+ const require_sf_cli = require("./sf-cli-R6LHwqoo.cjs");
3
+ const require_codegen = require("./codegen-Bylrt2Ju.cjs");
4
4
  let node_fs = require("node:fs");
5
5
  let node_path = require("node:path");
6
6
  let node_util = require("node:util");
@@ -24,7 +24,7 @@ Options for init (skip the matching questions; useful in scripts):
24
24
  --sobjects <list> Comma-separated sObject API names
25
25
  --auth <type> clientCredentials | sfCli | accessToken | jwtBearer
26
26
  --target-org <alias> Salesforce CLI org alias (with --auth sfCli)
27
- --picklists <mode> union | string
27
+ --picklists <mode> union | string | const | enum
28
28
  --format <format> ts | json
29
29
  -y, --yes Use the suggested answer for every other question (needs --api-version)
30
30
  -f, --force Replace an existing config without asking
@@ -111,7 +111,7 @@ function initPreset(values) {
111
111
  "jwtBearer"
112
112
  ]);
113
113
  if (values["target-org"] !== void 0) preset.targetOrg = values["target-org"];
114
- const picklists = oneOf("--picklists", values.picklists, ["union", "string"]);
114
+ const picklists = oneOf("--picklists", values.picklists, require_codegen.PICKLIST_MODES);
115
115
  const format = oneOf("--format", values.format, ["ts", "json"]);
116
116
  return {
117
117
  ...preset,
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.cjs","names":["parseArgs","readlinePrompter","runInit","normalizeApiVersion","parseSObjects","resolve","existsSync","findConfigFiles","loadConfig","checkGenerated","generate"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/* oxlint-disable no-console */\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport { normalizeApiVersion } from \"./api-version\";\nimport { checkGenerated, findConfigFiles, generate, loadConfig } from \"./codegen/index\";\nimport { type InitAnswers, parseSObjects, runInit } from \"./codegen/init\";\nimport { readlinePrompter } from \"./codegen/prompter\";\n\nconst HELP = `Usage: sobjectly <command> [options]\n\nCommands:\n init Create a sobjectly.config file by answering a few questions\n generate Generate TypeScript types for Salesforce sObjects (default)\n\nOptions for generate:\n -c, --config <path> Config file (default: sobjectly.config.ts or sobjectly.config.json)\n -e, --env-file <path> Load environment variables from a .env file first\n --check Don't write; exit with 1 when the generated file is out of date\n (formatting-only differences don't count)\n --stdout Print the generated source instead of writing it\n\nOptions for init (skip the matching questions; useful in scripts):\n --api-version <v> Salesforce API version, e.g. v66.0\n --output <path> Generated types file, e.g. src/generated/sobjects.ts\n --sobjects <list> Comma-separated sObject API names\n --auth <type> clientCredentials | sfCli | accessToken | jwtBearer\n --target-org <alias> Salesforce CLI org alias (with --auth sfCli)\n --picklists <mode> union | string\n --format <format> ts | json\n -y, --yes Use the suggested answer for every other question (needs --api-version)\n -f, --force Replace an existing config without asking\n\n -h, --help Show this help\n`;\n\nconst options = {\n\tconfig: { type: \"string\", short: \"c\" },\n\t\"env-file\": { type: \"string\", short: \"e\" },\n\t\"api-version\": { type: \"string\" },\n\toutput: { type: \"string\" },\n\tsobjects: { type: \"string\" },\n\tauth: { type: \"string\" },\n\tpicklists: { type: \"string\" },\n\tformat: { type: \"string\" },\n\tcheck: { type: \"boolean\" },\n\tstdout: { type: \"boolean\" },\n\t\"target-org\": { type: \"string\" },\n\tyes: { type: \"boolean\", short: \"y\" },\n\tforce: { type: \"boolean\", short: \"f\" },\n\thelp: { type: \"boolean\", short: \"h\" },\n} as const;\n\ninterface Values {\n\tconfig?: string;\n\t\"env-file\"?: string;\n\t\"api-version\"?: string;\n\toutput?: string;\n\tsobjects?: string;\n\tauth?: string;\n\tpicklists?: string;\n\tformat?: string;\n\tcheck?: boolean;\n\tstdout?: boolean;\n\t\"target-org\"?: string;\n\tyes?: boolean;\n\tforce?: boolean;\n\thelp?: boolean;\n}\n\nasync function main(argv: string[]): Promise<number> {\n\tconst { values, positionals } = parseArgs({ args: argv, allowPositionals: true, options });\n\tconst command = positionals[0] ?? \"generate\";\n\tif (values.help) {\n\t\tconsole.log(HELP);\n\t\treturn 0;\n\t}\n\tif (command === \"init\") {\n\t\treturn init(values);\n\t}\n\tif (command === \"generate\") {\n\t\treturn runGenerate(values);\n\t}\n\tconsole.error(`Unknown command \"${command}\".\\n\\n${HELP}`);\n\treturn 1;\n}\n\nasync function init(values: Values): Promise<number> {\n\tconst preset = initPreset(values);\n\tconst prompter = readlinePrompter();\n\ttry {\n\t\tawait runInit({\n\t\t\tcwd: process.cwd(),\n\t\t\tprompter,\n\t\t\tlog: (message) => console.log(message),\n\t\t\tpreset,\n\t\t\tyes: values.yes,\n\t\t\tforce: values.force,\n\t\t});\n\t\treturn 0;\n\t} finally {\n\t\tprompter.close();\n\t}\n}\n\nfunction initPreset(values: Values): Partial<InitAnswers> {\n\tconst preset: Partial<InitAnswers> = {};\n\tif (values[\"api-version\"] !== undefined) {\n\t\tconst apiVersion = normalizeApiVersion(values[\"api-version\"]);\n\t\tif (!apiVersion) {\n\t\t\tthrow new Error(`Invalid --api-version \"${values[\"api-version\"]}\". Expected a value like v66.0.`);\n\t\t}\n\t\tpreset.apiVersion = apiVersion;\n\t}\n\tif (values.output !== undefined) {\n\t\tpreset.output = values.output;\n\t}\n\tif (values.sobjects !== undefined) {\n\t\tpreset.sobjects = parseSObjects(values.sobjects);\n\t}\n\tconst auth = oneOf(\"--auth\", values.auth, [\"clientCredentials\", \"sfCli\", \"accessToken\", \"jwtBearer\"] as const);\n\tif (values[\"target-org\"] !== undefined) {\n\t\tpreset.targetOrg = values[\"target-org\"];\n\t}\n\tconst picklists = oneOf(\"--picklists\", values.picklists, [\"union\", \"string\"] as const);\n\tconst format = oneOf(\"--format\", values.format, [\"ts\", \"json\"] as const);\n\treturn { ...preset, ...(auth && { auth }), ...(picklists && { picklists }), ...(format && { format }) };\n}\n\nfunction oneOf<T extends string>(flag: string, value: string | undefined, allowed: readonly T[]): T | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\tconst match = allowed.find((item) => item === value);\n\tif (!match) {\n\t\tthrow new Error(`Invalid ${flag} \"${value}\". Expected one of: ${allowed.join(\", \")}.`);\n\t}\n\treturn match;\n}\n\nasync function runGenerate(values: Values): Promise<number> {\n\tif (values[\"env-file\"]) {\n\t\tconst envFile = resolve(values[\"env-file\"]);\n\t\tif (!existsSync(envFile)) {\n\t\t\tconsole.error(`Env file not found: ${envFile}`);\n\t\t\treturn 1;\n\t\t}\n\t\tprocess.loadEnvFile(envFile);\n\t}\n\tconst found = findConfigFiles();\n\tif (!values.config && found.length > 1) {\n\t\tconsole.warn(`warning: found ${found.join(\" and \")}; using ${found[0]}. Remove the other one.`);\n\t}\n\tconst configPath = values.config ? resolve(values.config) : found[0];\n\tif (!configPath) {\n\t\tconsole.error('No sobjectly config file found. Run \"npx sobjectly init\" to create one, or pass --config.');\n\t\treturn 1;\n\t}\n\tconst { config, directory } = await loadConfig(configPath);\n\t// With --stdout the source goes to stdout, so progress goes to stderr.\n\tconst log = values.stdout\n\t\t? (message: string): void => console.error(message)\n\t\t: (message: string): void => console.log(message);\n\tconst logger = { info: log, warn: (message: string): void => console.warn(`warning: ${message}`) };\n\tif (values.check) {\n\t\treturn reportCheck(await checkGenerated(config, { cwd: directory, logger }));\n\t}\n\tconst result = await generate(config, { cwd: directory, logger, write: !values.stdout });\n\tif (values.stdout) {\n\t\tprocess.stdout.write(result.source);\n\t}\n\tif (result.skipped.length > 0) {\n\t\tconsole.warn(`Skipped ${result.skipped.length} sObject(s): ${result.skipped.map((item) => item.name).join(\", \")}`);\n\t}\n\treturn 0;\n}\n\nfunction reportCheck(result: Awaited<ReturnType<typeof checkGenerated>>): number {\n\tif (result.upToDate) {\n\t\tconsole.log(`${result.outputPath} is up to date.`);\n\t\treturn 0;\n\t}\n\tif (result.reason === \"missing\") {\n\t\tconsole.error(`${result.outputPath} does not exist. Run \"sobjectly generate\".`);\n\t\treturn 1;\n\t}\n\tconst details = [\n\t\tresult.added.length > 0 ? `added: ${result.added.join(\", \")}` : \"\",\n\t\tresult.removed.length > 0 ? `removed: ${result.removed.join(\", \")}` : \"\",\n\t\tresult.changed.length > 0 ? `changed: ${result.changed.join(\", \")}` : \"\",\n\t].filter((detail) => detail.length > 0);\n\tconsole.error(\n\t\t`${result.outputPath} is out of date${details.length > 0 ? ` (${details.join(\"; \")})` : \"\"}. Run \"sobjectly generate\".`,\n\t);\n\treturn 1;\n}\n\nmain(process.argv.slice(2))\n\t.then((code) => {\n\t\tprocess.exitCode = code;\n\t})\n\t.catch((error: unknown) => {\n\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\tprocess.exitCode = 1;\n\t});\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.cjs","names":["parseArgs","readlinePrompter","runInit","normalizeApiVersion","parseSObjects","PICKLIST_MODES","resolve","existsSync","findConfigFiles","loadConfig","checkGenerated","generate"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/* oxlint-disable no-console */\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport { normalizeApiVersion } from \"./api-version\";\nimport { checkGenerated, findConfigFiles, generate, loadConfig } from \"./codegen/index\";\nimport { type InitAnswers, parseSObjects, runInit } from \"./codegen/init\";\nimport { readlinePrompter } from \"./codegen/prompter\";\nimport { PICKLIST_MODES } from \"./codegen/validate-config\";\n\nconst HELP = `Usage: sobjectly <command> [options]\n\nCommands:\n init Create a sobjectly.config file by answering a few questions\n generate Generate TypeScript types for Salesforce sObjects (default)\n\nOptions for generate:\n -c, --config <path> Config file (default: sobjectly.config.ts or sobjectly.config.json)\n -e, --env-file <path> Load environment variables from a .env file first\n --check Don't write; exit with 1 when the generated file is out of date\n (formatting-only differences don't count)\n --stdout Print the generated source instead of writing it\n\nOptions for init (skip the matching questions; useful in scripts):\n --api-version <v> Salesforce API version, e.g. v66.0\n --output <path> Generated types file, e.g. src/generated/sobjects.ts\n --sobjects <list> Comma-separated sObject API names\n --auth <type> clientCredentials | sfCli | accessToken | jwtBearer\n --target-org <alias> Salesforce CLI org alias (with --auth sfCli)\n --picklists <mode> union | string | const | enum\n --format <format> ts | json\n -y, --yes Use the suggested answer for every other question (needs --api-version)\n -f, --force Replace an existing config without asking\n\n -h, --help Show this help\n`;\n\nconst options = {\n\tconfig: { type: \"string\", short: \"c\" },\n\t\"env-file\": { type: \"string\", short: \"e\" },\n\t\"api-version\": { type: \"string\" },\n\toutput: { type: \"string\" },\n\tsobjects: { type: \"string\" },\n\tauth: { type: \"string\" },\n\tpicklists: { type: \"string\" },\n\tformat: { type: \"string\" },\n\tcheck: { type: \"boolean\" },\n\tstdout: { type: \"boolean\" },\n\t\"target-org\": { type: \"string\" },\n\tyes: { type: \"boolean\", short: \"y\" },\n\tforce: { type: \"boolean\", short: \"f\" },\n\thelp: { type: \"boolean\", short: \"h\" },\n} as const;\n\ninterface Values {\n\tconfig?: string;\n\t\"env-file\"?: string;\n\t\"api-version\"?: string;\n\toutput?: string;\n\tsobjects?: string;\n\tauth?: string;\n\tpicklists?: string;\n\tformat?: string;\n\tcheck?: boolean;\n\tstdout?: boolean;\n\t\"target-org\"?: string;\n\tyes?: boolean;\n\tforce?: boolean;\n\thelp?: boolean;\n}\n\nasync function main(argv: string[]): Promise<number> {\n\tconst { values, positionals } = parseArgs({ args: argv, allowPositionals: true, options });\n\tconst command = positionals[0] ?? \"generate\";\n\tif (values.help) {\n\t\tconsole.log(HELP);\n\t\treturn 0;\n\t}\n\tif (command === \"init\") {\n\t\treturn init(values);\n\t}\n\tif (command === \"generate\") {\n\t\treturn runGenerate(values);\n\t}\n\tconsole.error(`Unknown command \"${command}\".\\n\\n${HELP}`);\n\treturn 1;\n}\n\nasync function init(values: Values): Promise<number> {\n\tconst preset = initPreset(values);\n\tconst prompter = readlinePrompter();\n\ttry {\n\t\tawait runInit({\n\t\t\tcwd: process.cwd(),\n\t\t\tprompter,\n\t\t\tlog: (message) => console.log(message),\n\t\t\tpreset,\n\t\t\tyes: values.yes,\n\t\t\tforce: values.force,\n\t\t});\n\t\treturn 0;\n\t} finally {\n\t\tprompter.close();\n\t}\n}\n\nfunction initPreset(values: Values): Partial<InitAnswers> {\n\tconst preset: Partial<InitAnswers> = {};\n\tif (values[\"api-version\"] !== undefined) {\n\t\tconst apiVersion = normalizeApiVersion(values[\"api-version\"]);\n\t\tif (!apiVersion) {\n\t\t\tthrow new Error(`Invalid --api-version \"${values[\"api-version\"]}\". Expected a value like v66.0.`);\n\t\t}\n\t\tpreset.apiVersion = apiVersion;\n\t}\n\tif (values.output !== undefined) {\n\t\tpreset.output = values.output;\n\t}\n\tif (values.sobjects !== undefined) {\n\t\tpreset.sobjects = parseSObjects(values.sobjects);\n\t}\n\tconst auth = oneOf(\"--auth\", values.auth, [\"clientCredentials\", \"sfCli\", \"accessToken\", \"jwtBearer\"] as const);\n\tif (values[\"target-org\"] !== undefined) {\n\t\tpreset.targetOrg = values[\"target-org\"];\n\t}\n\tconst picklists = oneOf(\"--picklists\", values.picklists, PICKLIST_MODES);\n\tconst format = oneOf(\"--format\", values.format, [\"ts\", \"json\"] as const);\n\treturn { ...preset, ...(auth && { auth }), ...(picklists && { picklists }), ...(format && { format }) };\n}\n\nfunction oneOf<T extends string>(flag: string, value: string | undefined, allowed: readonly T[]): T | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\tconst match = allowed.find((item) => item === value);\n\tif (!match) {\n\t\tthrow new Error(`Invalid ${flag} \"${value}\". Expected one of: ${allowed.join(\", \")}.`);\n\t}\n\treturn match;\n}\n\nasync function runGenerate(values: Values): Promise<number> {\n\tif (values[\"env-file\"]) {\n\t\tconst envFile = resolve(values[\"env-file\"]);\n\t\tif (!existsSync(envFile)) {\n\t\t\tconsole.error(`Env file not found: ${envFile}`);\n\t\t\treturn 1;\n\t\t}\n\t\tprocess.loadEnvFile(envFile);\n\t}\n\tconst found = findConfigFiles();\n\tif (!values.config && found.length > 1) {\n\t\tconsole.warn(`warning: found ${found.join(\" and \")}; using ${found[0]}. Remove the other one.`);\n\t}\n\tconst configPath = values.config ? resolve(values.config) : found[0];\n\tif (!configPath) {\n\t\tconsole.error('No sobjectly config file found. Run \"npx sobjectly init\" to create one, or pass --config.');\n\t\treturn 1;\n\t}\n\tconst { config, directory } = await loadConfig(configPath);\n\t// With --stdout the source goes to stdout, so progress goes to stderr.\n\tconst log = values.stdout\n\t\t? (message: string): void => console.error(message)\n\t\t: (message: string): void => console.log(message);\n\tconst logger = { info: log, warn: (message: string): void => console.warn(`warning: ${message}`) };\n\tif (values.check) {\n\t\treturn reportCheck(await checkGenerated(config, { cwd: directory, logger }));\n\t}\n\tconst result = await generate(config, { cwd: directory, logger, write: !values.stdout });\n\tif (values.stdout) {\n\t\tprocess.stdout.write(result.source);\n\t}\n\tif (result.skipped.length > 0) {\n\t\tconsole.warn(`Skipped ${result.skipped.length} sObject(s): ${result.skipped.map((item) => item.name).join(\", \")}`);\n\t}\n\treturn 0;\n}\n\nfunction reportCheck(result: Awaited<ReturnType<typeof checkGenerated>>): number {\n\tif (result.upToDate) {\n\t\tconsole.log(`${result.outputPath} is up to date.`);\n\t\treturn 0;\n\t}\n\tif (result.reason === \"missing\") {\n\t\tconsole.error(`${result.outputPath} does not exist. Run \"sobjectly generate\".`);\n\t\treturn 1;\n\t}\n\tconst details = [\n\t\tresult.added.length > 0 ? `added: ${result.added.join(\", \")}` : \"\",\n\t\tresult.removed.length > 0 ? `removed: ${result.removed.join(\", \")}` : \"\",\n\t\tresult.changed.length > 0 ? `changed: ${result.changed.join(\", \")}` : \"\",\n\t].filter((detail) => detail.length > 0);\n\tconsole.error(\n\t\t`${result.outputPath} is out of date${details.length > 0 ? ` (${details.join(\"; \")})` : \"\"}. Run \"sobjectly generate\".`,\n\t);\n\treturn 1;\n}\n\nmain(process.argv.slice(2))\n\t.then((code) => {\n\t\tprocess.exitCode = code;\n\t})\n\t.catch((error: unknown) => {\n\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\tprocess.exitCode = 1;\n\t});\n"],"mappings":"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"}
package/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { Lt as __commonJSMin, Mt as init_api_version, Pt as normalizeApiVersion } from "./sf-cli-CtWtdkJ0.mjs";
3
- import { a as init_prompter, c as init_init, d as runInit, g as loadConfig, l as parseSObjects, m as findConfigFiles, n as generate, o as readlinePrompter, r as init_codegen, t as checkGenerated } from "./codegen-CSx0WAtK.mjs";
2
+ import { Lt as __commonJSMin, Mt as init_api_version, Pt as normalizeApiVersion } from "./sf-cli-_Ua-M_Zi.mjs";
3
+ import { C as init_validate_config, S as PICKLIST_MODES, a as init_prompter, c as init_init, d as runInit, g as loadConfig, l as parseSObjects, m as findConfigFiles, n as generate, o as readlinePrompter, r as init_codegen, t as checkGenerated } from "./codegen-C6MEYiWo.mjs";
4
4
  import { existsSync } from "node:fs";
5
5
  import { resolve } from "node:path";
6
6
  import { parseArgs } from "node:util";
@@ -10,6 +10,7 @@ var require_cli = /* @__PURE__ */ __commonJSMin((() => {
10
10
  init_codegen();
11
11
  init_init();
12
12
  init_prompter();
13
+ init_validate_config();
13
14
  const HELP = `Usage: sobjectly <command> [options]
14
15
 
15
16
  Commands:
@@ -29,7 +30,7 @@ Options for init (skip the matching questions; useful in scripts):
29
30
  --sobjects <list> Comma-separated sObject API names
30
31
  --auth <type> clientCredentials | sfCli | accessToken | jwtBearer
31
32
  --target-org <alias> Salesforce CLI org alias (with --auth sfCli)
32
- --picklists <mode> union | string
33
+ --picklists <mode> union | string | const | enum
33
34
  --format <format> ts | json
34
35
  -y, --yes Use the suggested answer for every other question (needs --api-version)
35
36
  -f, --force Replace an existing config without asking
@@ -116,7 +117,7 @@ Options for init (skip the matching questions; useful in scripts):
116
117
  "jwtBearer"
117
118
  ]);
118
119
  if (values["target-org"] !== void 0) preset.targetOrg = values["target-org"];
119
- const picklists = oneOf("--picklists", values.picklists, ["union", "string"]);
120
+ const picklists = oneOf("--picklists", values.picklists, PICKLIST_MODES);
120
121
  const format = oneOf("--format", values.format, ["ts", "json"]);
121
122
  return {
122
123
  ...preset,
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/* oxlint-disable no-console */\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport { normalizeApiVersion } from \"./api-version\";\nimport { checkGenerated, findConfigFiles, generate, loadConfig } from \"./codegen/index\";\nimport { type InitAnswers, parseSObjects, runInit } from \"./codegen/init\";\nimport { readlinePrompter } from \"./codegen/prompter\";\n\nconst HELP = `Usage: sobjectly <command> [options]\n\nCommands:\n init Create a sobjectly.config file by answering a few questions\n generate Generate TypeScript types for Salesforce sObjects (default)\n\nOptions for generate:\n -c, --config <path> Config file (default: sobjectly.config.ts or sobjectly.config.json)\n -e, --env-file <path> Load environment variables from a .env file first\n --check Don't write; exit with 1 when the generated file is out of date\n (formatting-only differences don't count)\n --stdout Print the generated source instead of writing it\n\nOptions for init (skip the matching questions; useful in scripts):\n --api-version <v> Salesforce API version, e.g. v66.0\n --output <path> Generated types file, e.g. src/generated/sobjects.ts\n --sobjects <list> Comma-separated sObject API names\n --auth <type> clientCredentials | sfCli | accessToken | jwtBearer\n --target-org <alias> Salesforce CLI org alias (with --auth sfCli)\n --picklists <mode> union | string\n --format <format> ts | json\n -y, --yes Use the suggested answer for every other question (needs --api-version)\n -f, --force Replace an existing config without asking\n\n -h, --help Show this help\n`;\n\nconst options = {\n\tconfig: { type: \"string\", short: \"c\" },\n\t\"env-file\": { type: \"string\", short: \"e\" },\n\t\"api-version\": { type: \"string\" },\n\toutput: { type: \"string\" },\n\tsobjects: { type: \"string\" },\n\tauth: { type: \"string\" },\n\tpicklists: { type: \"string\" },\n\tformat: { type: \"string\" },\n\tcheck: { type: \"boolean\" },\n\tstdout: { type: \"boolean\" },\n\t\"target-org\": { type: \"string\" },\n\tyes: { type: \"boolean\", short: \"y\" },\n\tforce: { type: \"boolean\", short: \"f\" },\n\thelp: { type: \"boolean\", short: \"h\" },\n} as const;\n\ninterface Values {\n\tconfig?: string;\n\t\"env-file\"?: string;\n\t\"api-version\"?: string;\n\toutput?: string;\n\tsobjects?: string;\n\tauth?: string;\n\tpicklists?: string;\n\tformat?: string;\n\tcheck?: boolean;\n\tstdout?: boolean;\n\t\"target-org\"?: string;\n\tyes?: boolean;\n\tforce?: boolean;\n\thelp?: boolean;\n}\n\nasync function main(argv: string[]): Promise<number> {\n\tconst { values, positionals } = parseArgs({ args: argv, allowPositionals: true, options });\n\tconst command = positionals[0] ?? \"generate\";\n\tif (values.help) {\n\t\tconsole.log(HELP);\n\t\treturn 0;\n\t}\n\tif (command === \"init\") {\n\t\treturn init(values);\n\t}\n\tif (command === \"generate\") {\n\t\treturn runGenerate(values);\n\t}\n\tconsole.error(`Unknown command \"${command}\".\\n\\n${HELP}`);\n\treturn 1;\n}\n\nasync function init(values: Values): Promise<number> {\n\tconst preset = initPreset(values);\n\tconst prompter = readlinePrompter();\n\ttry {\n\t\tawait runInit({\n\t\t\tcwd: process.cwd(),\n\t\t\tprompter,\n\t\t\tlog: (message) => console.log(message),\n\t\t\tpreset,\n\t\t\tyes: values.yes,\n\t\t\tforce: values.force,\n\t\t});\n\t\treturn 0;\n\t} finally {\n\t\tprompter.close();\n\t}\n}\n\nfunction initPreset(values: Values): Partial<InitAnswers> {\n\tconst preset: Partial<InitAnswers> = {};\n\tif (values[\"api-version\"] !== undefined) {\n\t\tconst apiVersion = normalizeApiVersion(values[\"api-version\"]);\n\t\tif (!apiVersion) {\n\t\t\tthrow new Error(`Invalid --api-version \"${values[\"api-version\"]}\". Expected a value like v66.0.`);\n\t\t}\n\t\tpreset.apiVersion = apiVersion;\n\t}\n\tif (values.output !== undefined) {\n\t\tpreset.output = values.output;\n\t}\n\tif (values.sobjects !== undefined) {\n\t\tpreset.sobjects = parseSObjects(values.sobjects);\n\t}\n\tconst auth = oneOf(\"--auth\", values.auth, [\"clientCredentials\", \"sfCli\", \"accessToken\", \"jwtBearer\"] as const);\n\tif (values[\"target-org\"] !== undefined) {\n\t\tpreset.targetOrg = values[\"target-org\"];\n\t}\n\tconst picklists = oneOf(\"--picklists\", values.picklists, [\"union\", \"string\"] as const);\n\tconst format = oneOf(\"--format\", values.format, [\"ts\", \"json\"] as const);\n\treturn { ...preset, ...(auth && { auth }), ...(picklists && { picklists }), ...(format && { format }) };\n}\n\nfunction oneOf<T extends string>(flag: string, value: string | undefined, allowed: readonly T[]): T | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\tconst match = allowed.find((item) => item === value);\n\tif (!match) {\n\t\tthrow new Error(`Invalid ${flag} \"${value}\". Expected one of: ${allowed.join(\", \")}.`);\n\t}\n\treturn match;\n}\n\nasync function runGenerate(values: Values): Promise<number> {\n\tif (values[\"env-file\"]) {\n\t\tconst envFile = resolve(values[\"env-file\"]);\n\t\tif (!existsSync(envFile)) {\n\t\t\tconsole.error(`Env file not found: ${envFile}`);\n\t\t\treturn 1;\n\t\t}\n\t\tprocess.loadEnvFile(envFile);\n\t}\n\tconst found = findConfigFiles();\n\tif (!values.config && found.length > 1) {\n\t\tconsole.warn(`warning: found ${found.join(\" and \")}; using ${found[0]}. Remove the other one.`);\n\t}\n\tconst configPath = values.config ? resolve(values.config) : found[0];\n\tif (!configPath) {\n\t\tconsole.error('No sobjectly config file found. Run \"npx sobjectly init\" to create one, or pass --config.');\n\t\treturn 1;\n\t}\n\tconst { config, directory } = await loadConfig(configPath);\n\t// With --stdout the source goes to stdout, so progress goes to stderr.\n\tconst log = values.stdout\n\t\t? (message: string): void => console.error(message)\n\t\t: (message: string): void => console.log(message);\n\tconst logger = { info: log, warn: (message: string): void => console.warn(`warning: ${message}`) };\n\tif (values.check) {\n\t\treturn reportCheck(await checkGenerated(config, { cwd: directory, logger }));\n\t}\n\tconst result = await generate(config, { cwd: directory, logger, write: !values.stdout });\n\tif (values.stdout) {\n\t\tprocess.stdout.write(result.source);\n\t}\n\tif (result.skipped.length > 0) {\n\t\tconsole.warn(`Skipped ${result.skipped.length} sObject(s): ${result.skipped.map((item) => item.name).join(\", \")}`);\n\t}\n\treturn 0;\n}\n\nfunction reportCheck(result: Awaited<ReturnType<typeof checkGenerated>>): number {\n\tif (result.upToDate) {\n\t\tconsole.log(`${result.outputPath} is up to date.`);\n\t\treturn 0;\n\t}\n\tif (result.reason === \"missing\") {\n\t\tconsole.error(`${result.outputPath} does not exist. Run \"sobjectly generate\".`);\n\t\treturn 1;\n\t}\n\tconst details = [\n\t\tresult.added.length > 0 ? `added: ${result.added.join(\", \")}` : \"\",\n\t\tresult.removed.length > 0 ? `removed: ${result.removed.join(\", \")}` : \"\",\n\t\tresult.changed.length > 0 ? `changed: ${result.changed.join(\", \")}` : \"\",\n\t].filter((detail) => detail.length > 0);\n\tconsole.error(\n\t\t`${result.outputPath} is out of date${details.length > 0 ? ` (${details.join(\"; \")})` : \"\"}. Run \"sobjectly generate\".`,\n\t);\n\treturn 1;\n}\n\nmain(process.argv.slice(2))\n\t.then((code) => {\n\t\tprocess.exitCode = code;\n\t})\n\t.catch((error: unknown) => {\n\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\tprocess.exitCode = 1;\n\t});\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/* oxlint-disable no-console */\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport { normalizeApiVersion } from \"./api-version\";\nimport { checkGenerated, findConfigFiles, generate, loadConfig } from \"./codegen/index\";\nimport { type InitAnswers, parseSObjects, runInit } from \"./codegen/init\";\nimport { readlinePrompter } from \"./codegen/prompter\";\nimport { PICKLIST_MODES } from \"./codegen/validate-config\";\n\nconst HELP = `Usage: sobjectly <command> [options]\n\nCommands:\n init Create a sobjectly.config file by answering a few questions\n generate Generate TypeScript types for Salesforce sObjects (default)\n\nOptions for generate:\n -c, --config <path> Config file (default: sobjectly.config.ts or sobjectly.config.json)\n -e, --env-file <path> Load environment variables from a .env file first\n --check Don't write; exit with 1 when the generated file is out of date\n (formatting-only differences don't count)\n --stdout Print the generated source instead of writing it\n\nOptions for init (skip the matching questions; useful in scripts):\n --api-version <v> Salesforce API version, e.g. v66.0\n --output <path> Generated types file, e.g. src/generated/sobjects.ts\n --sobjects <list> Comma-separated sObject API names\n --auth <type> clientCredentials | sfCli | accessToken | jwtBearer\n --target-org <alias> Salesforce CLI org alias (with --auth sfCli)\n --picklists <mode> union | string | const | enum\n --format <format> ts | json\n -y, --yes Use the suggested answer for every other question (needs --api-version)\n -f, --force Replace an existing config without asking\n\n -h, --help Show this help\n`;\n\nconst options = {\n\tconfig: { type: \"string\", short: \"c\" },\n\t\"env-file\": { type: \"string\", short: \"e\" },\n\t\"api-version\": { type: \"string\" },\n\toutput: { type: \"string\" },\n\tsobjects: { type: \"string\" },\n\tauth: { type: \"string\" },\n\tpicklists: { type: \"string\" },\n\tformat: { type: \"string\" },\n\tcheck: { type: \"boolean\" },\n\tstdout: { type: \"boolean\" },\n\t\"target-org\": { type: \"string\" },\n\tyes: { type: \"boolean\", short: \"y\" },\n\tforce: { type: \"boolean\", short: \"f\" },\n\thelp: { type: \"boolean\", short: \"h\" },\n} as const;\n\ninterface Values {\n\tconfig?: string;\n\t\"env-file\"?: string;\n\t\"api-version\"?: string;\n\toutput?: string;\n\tsobjects?: string;\n\tauth?: string;\n\tpicklists?: string;\n\tformat?: string;\n\tcheck?: boolean;\n\tstdout?: boolean;\n\t\"target-org\"?: string;\n\tyes?: boolean;\n\tforce?: boolean;\n\thelp?: boolean;\n}\n\nasync function main(argv: string[]): Promise<number> {\n\tconst { values, positionals } = parseArgs({ args: argv, allowPositionals: true, options });\n\tconst command = positionals[0] ?? \"generate\";\n\tif (values.help) {\n\t\tconsole.log(HELP);\n\t\treturn 0;\n\t}\n\tif (command === \"init\") {\n\t\treturn init(values);\n\t}\n\tif (command === \"generate\") {\n\t\treturn runGenerate(values);\n\t}\n\tconsole.error(`Unknown command \"${command}\".\\n\\n${HELP}`);\n\treturn 1;\n}\n\nasync function init(values: Values): Promise<number> {\n\tconst preset = initPreset(values);\n\tconst prompter = readlinePrompter();\n\ttry {\n\t\tawait runInit({\n\t\t\tcwd: process.cwd(),\n\t\t\tprompter,\n\t\t\tlog: (message) => console.log(message),\n\t\t\tpreset,\n\t\t\tyes: values.yes,\n\t\t\tforce: values.force,\n\t\t});\n\t\treturn 0;\n\t} finally {\n\t\tprompter.close();\n\t}\n}\n\nfunction initPreset(values: Values): Partial<InitAnswers> {\n\tconst preset: Partial<InitAnswers> = {};\n\tif (values[\"api-version\"] !== undefined) {\n\t\tconst apiVersion = normalizeApiVersion(values[\"api-version\"]);\n\t\tif (!apiVersion) {\n\t\t\tthrow new Error(`Invalid --api-version \"${values[\"api-version\"]}\". Expected a value like v66.0.`);\n\t\t}\n\t\tpreset.apiVersion = apiVersion;\n\t}\n\tif (values.output !== undefined) {\n\t\tpreset.output = values.output;\n\t}\n\tif (values.sobjects !== undefined) {\n\t\tpreset.sobjects = parseSObjects(values.sobjects);\n\t}\n\tconst auth = oneOf(\"--auth\", values.auth, [\"clientCredentials\", \"sfCli\", \"accessToken\", \"jwtBearer\"] as const);\n\tif (values[\"target-org\"] !== undefined) {\n\t\tpreset.targetOrg = values[\"target-org\"];\n\t}\n\tconst picklists = oneOf(\"--picklists\", values.picklists, PICKLIST_MODES);\n\tconst format = oneOf(\"--format\", values.format, [\"ts\", \"json\"] as const);\n\treturn { ...preset, ...(auth && { auth }), ...(picklists && { picklists }), ...(format && { format }) };\n}\n\nfunction oneOf<T extends string>(flag: string, value: string | undefined, allowed: readonly T[]): T | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\tconst match = allowed.find((item) => item === value);\n\tif (!match) {\n\t\tthrow new Error(`Invalid ${flag} \"${value}\". Expected one of: ${allowed.join(\", \")}.`);\n\t}\n\treturn match;\n}\n\nasync function runGenerate(values: Values): Promise<number> {\n\tif (values[\"env-file\"]) {\n\t\tconst envFile = resolve(values[\"env-file\"]);\n\t\tif (!existsSync(envFile)) {\n\t\t\tconsole.error(`Env file not found: ${envFile}`);\n\t\t\treturn 1;\n\t\t}\n\t\tprocess.loadEnvFile(envFile);\n\t}\n\tconst found = findConfigFiles();\n\tif (!values.config && found.length > 1) {\n\t\tconsole.warn(`warning: found ${found.join(\" and \")}; using ${found[0]}. Remove the other one.`);\n\t}\n\tconst configPath = values.config ? resolve(values.config) : found[0];\n\tif (!configPath) {\n\t\tconsole.error('No sobjectly config file found. Run \"npx sobjectly init\" to create one, or pass --config.');\n\t\treturn 1;\n\t}\n\tconst { config, directory } = await loadConfig(configPath);\n\t// With --stdout the source goes to stdout, so progress goes to stderr.\n\tconst log = values.stdout\n\t\t? (message: string): void => console.error(message)\n\t\t: (message: string): void => console.log(message);\n\tconst logger = { info: log, warn: (message: string): void => console.warn(`warning: ${message}`) };\n\tif (values.check) {\n\t\treturn reportCheck(await checkGenerated(config, { cwd: directory, logger }));\n\t}\n\tconst result = await generate(config, { cwd: directory, logger, write: !values.stdout });\n\tif (values.stdout) {\n\t\tprocess.stdout.write(result.source);\n\t}\n\tif (result.skipped.length > 0) {\n\t\tconsole.warn(`Skipped ${result.skipped.length} sObject(s): ${result.skipped.map((item) => item.name).join(\", \")}`);\n\t}\n\treturn 0;\n}\n\nfunction reportCheck(result: Awaited<ReturnType<typeof checkGenerated>>): number {\n\tif (result.upToDate) {\n\t\tconsole.log(`${result.outputPath} is up to date.`);\n\t\treturn 0;\n\t}\n\tif (result.reason === \"missing\") {\n\t\tconsole.error(`${result.outputPath} does not exist. Run \"sobjectly generate\".`);\n\t\treturn 1;\n\t}\n\tconst details = [\n\t\tresult.added.length > 0 ? `added: ${result.added.join(\", \")}` : \"\",\n\t\tresult.removed.length > 0 ? `removed: ${result.removed.join(\", \")}` : \"\",\n\t\tresult.changed.length > 0 ? `changed: ${result.changed.join(\", \")}` : \"\",\n\t].filter((detail) => detail.length > 0);\n\tconsole.error(\n\t\t`${result.outputPath} is out of date${details.length > 0 ? ` (${details.join(\"; \")})` : \"\"}. Run \"sobjectly generate\".`,\n\t);\n\treturn 1;\n}\n\nmain(process.argv.slice(2))\n\t.then((code) => {\n\t\tprocess.exitCode = code;\n\t})\n\t.catch((error: unknown) => {\n\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\tprocess.exitCode = 1;\n\t});\n"],"mappings":"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"}
@@ -1,10 +1,28 @@
1
- const require_sf_cli = require("./sf-cli-DEVZQTR7.cjs");
1
+ const require_sf_cli = require("./sf-cli-R6LHwqoo.cjs");
2
2
  let node_crypto = require("node:crypto");
3
3
  let node_fs = require("node:fs");
4
4
  let node_fs_promises = require("node:fs/promises");
5
5
  let node_path = require("node:path");
6
6
  let jiti = require("jiti");
7
7
  let node_readline = require("node:readline");
8
+ //#region src/codegen/emit.ts
9
+ /** Helpers shared by the modules that emit generated source. */
10
+ const IDENTIFIER$1 = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
11
+ /** Fields sorted by name, `Id` first, so the output is deterministic. */
12
+ function sortedFields(describe) {
13
+ return [...describe.fields].sort((a, b) => a.name === "Id" ? -1 : b.name === "Id" ? 1 : compare(a.name, b.name));
14
+ }
15
+ /** An object key: the bare name when it is an identifier, otherwise a quoted string. */
16
+ function propertyKey(name) {
17
+ return IDENTIFIER$1.test(name) ? name : JSON.stringify(name);
18
+ }
19
+ function escapeComment(value) {
20
+ return String(value ?? "").replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
21
+ }
22
+ function compare(a, b) {
23
+ return a < b ? -1 : a > b ? 1 : 0;
24
+ }
25
+ //#endregion
8
26
  //#region src/codegen/type-mapper.ts
9
27
  const ADDRESS_TYPE = "SalesforceAddress";
10
28
  const GEOLOCATION_TYPE = "SalesforceGeolocation";
@@ -39,17 +57,143 @@ const FIELD_TYPE_MAP = {
39
57
  address: ADDRESS_TYPE,
40
58
  location: GEOLOCATION_TYPE
41
59
  };
42
- /** Returns the TypeScript type for a field, including `| null` for nillable fields. */
43
- function mapFieldType(field, picklists = "union") {
44
- const base = picklistType(field, picklists) ?? FIELD_TYPE_MAP[field.type] ?? "unknown";
60
+ /**
61
+ * Returns the TypeScript type for a field, including `| null` for nillable fields. With picklist
62
+ * mode `"const"` or `"enum"`, `typeName` is the field's named picklist type; it replaces the union.
63
+ */
64
+ function mapFieldType(field, picklists = "union", typeName) {
65
+ const base = picklistType(field, picklists, typeName) ?? FIELD_TYPE_MAP[field.type] ?? "unknown";
45
66
  return field.nillable && base !== "unknown" ? `${base} | null` : base;
46
67
  }
47
- function picklistType(field, picklists) {
48
- if (picklists !== "union" || field.type !== "picklist" && field.type !== "combobox") return;
49
- const values = [...new Set((field.picklistValues ?? []).filter((entry) => entry.active).map((entry) => entry.value))];
68
+ function picklistType(field, picklists, typeName) {
69
+ if (picklists === "string" || field.type !== "picklist" && field.type !== "combobox") return;
70
+ const values = activePicklistValues(field);
50
71
  if (values.length === 0) return;
51
- const union = values.map((value) => JSON.stringify(value)).join(" | ");
52
- return field.restrictedPicklist && field.type === "picklist" ? union : `${union} | (string & {})`;
72
+ const members = picklists !== "union" && typeName !== void 0 ? typeName : values.map((value) => JSON.stringify(value)).join(" | ");
73
+ return field.restrictedPicklist && field.type === "picklist" ? members : `${members} | (string & {})`;
74
+ }
75
+ /** The distinct active values of a picklist, multi-select picklist or combobox, in describe order. */
76
+ function activePicklistValues(field) {
77
+ if (field.type !== "picklist" && field.type !== "multipicklist" && field.type !== "combobox") return [];
78
+ return [...new Set((field.picklistValues ?? []).filter((entry) => entry.active).map((entry) => entry.value))];
79
+ }
80
+ //#endregion
81
+ //#region src/codegen/picklist-types.ts
82
+ function isNamedPicklistMode(mode) {
83
+ return mode === "const" || mode === "enum";
84
+ }
85
+ /** Appended to a picklist type name that is already taken, e.g. by the `CaseStatus` sObject. */
86
+ const CLASH_SUFFIX = "Picklist";
87
+ /**
88
+ * Names the type of every picklist with active values: sObject and field in PascalCase, e.g.
89
+ * `Case.Status` -> `CaseStatus` and `Case.Status__c` -> `CaseStatusCustom`. A name that is in
90
+ * `taken` (the sObjects and other names the file defines) or already used by another picklist gets
91
+ * the suffix `Picklist`; when that is taken too, this throws.
92
+ */
93
+ function picklistTypeNames(describes, taken) {
94
+ const used = new Map([...taken].map((name) => [name, void 0]));
95
+ const result = /* @__PURE__ */ new Map();
96
+ for (const describe of [...describes].sort((a, b) => compare(a.name, b.name))) {
97
+ const names = /* @__PURE__ */ new Map();
98
+ for (const field of sortedFields(describe).filter((item) => activePicklistValues(item).length > 0)) {
99
+ const base = `${pascalApiName(describe.name, "sobject")}${pascalApiName(field.name, "field")}`;
100
+ if (IDENTIFIER$1.test(base)) {
101
+ const picklist = `${describe.name}.${field.name}`;
102
+ const name = resolveName(base, picklist, used);
103
+ used.set(name, picklist);
104
+ names.set(field.name, name);
105
+ }
106
+ }
107
+ if (names.size > 0) result.set(describe.name, names);
108
+ }
109
+ return result;
110
+ }
111
+ function resolveName(base, picklist, used) {
112
+ const name = used.has(base) ? `${base}${CLASH_SUFFIX}` : base;
113
+ if (used.has(name)) {
114
+ const owner = used.get(name);
115
+ const other = owner === void 0 ? "a type the generated file defines" : `the picklist type for ${owner}`;
116
+ throw new Error(`The picklist type "${name}" for ${picklist} clashes with ${other}; exclude one of the sObjects or use picklists: "union".`);
117
+ }
118
+ return name;
119
+ }
120
+ /**
121
+ * An API name in PascalCase without underscores. A field's `__c` becomes `Custom`
122
+ * (`Lead_Source__c` -> `LeadSourceCustom`) and an sObject's is dropped (`My_Object__c` ->
123
+ * `MyObject`). Other suffixes are kept: `Foo__pc` -> `FooPc`, `Config__mdt` -> `ConfigMdt`.
124
+ */
125
+ function pascalApiName(name, kind) {
126
+ const match = /^(.+?)__([a-z]+)$/.exec(name);
127
+ const body = match ? match[1] : name;
128
+ const suffix = match ? match[2] : "";
129
+ const suffixPart = suffix === "c" ? kind === "field" ? "Custom" : "" : capitalize(suffix);
130
+ return `${body.split("_").filter((part) => part.length > 0).map(capitalize).join("")}${suffixPart}`;
131
+ }
132
+ function capitalize(value) {
133
+ return value.charAt(0).toUpperCase() + value.slice(1);
134
+ }
135
+ /**
136
+ * The member names for a picklist's values, in the same order. A value that is an identifier is
137
+ * its own name; other values are sanitized (`"Closed Won"` -> `Closed_Won`, `"3rd Party"` ->
138
+ * `_3rd_Party`). When the sanitized name is empty or taken, the value itself is the (quoted) name.
139
+ */
140
+ function picklistMemberKeys(values, mode) {
141
+ const used = /* @__PURE__ */ new Set();
142
+ const claim = (key) => {
143
+ used.add(key);
144
+ return key;
145
+ };
146
+ const usable = (key) => key.length > 0 && key !== "__proto__" && !used.has(key) && (mode === "const" || !isNumericName(key));
147
+ const own = values.map((value) => IDENTIFIER$1.test(value) && usable(value) ? claim(value) : void 0);
148
+ return values.map((value, index) => {
149
+ const ownKey = own[index];
150
+ if (ownKey !== void 0) return ownKey;
151
+ const sanitized = sanitizeKey(value);
152
+ if (usable(sanitized)) return claim(sanitized);
153
+ if (usable(value)) return claim(value);
154
+ let counter = 2;
155
+ while (!usable(`${sanitized || "_"}_${counter}`)) counter++;
156
+ return claim(`${sanitized || "_"}_${counter}`);
157
+ });
158
+ }
159
+ function sanitizeKey(value) {
160
+ const key = value.normalize("NFKD").replace(/\p{M}/gu, "").replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
161
+ return /^[0-9]/.test(key) ? `_${key}` : key;
162
+ }
163
+ /** Names TypeScript rejects as enum members because they are numeric, e.g. `"1"` or `"-1"`. */
164
+ function isNumericName(key) {
165
+ const number = Number(key);
166
+ return Number.isFinite(number) && String(number) === key;
167
+ }
168
+ /** The named type declarations for one sObject's picklists, in field order; `""` when there are none. */
169
+ function picklistDeclarations(describe, names, mode) {
170
+ if (!names) return "";
171
+ return sortedFields(describe).flatMap((field) => {
172
+ const name = names.get(field.name);
173
+ return name === void 0 ? [] : [declaration(describe.name, field, name, mode)];
174
+ }).join("\n\n");
175
+ }
176
+ function declaration(sobject, field, name, mode) {
177
+ const values = activePicklistValues(field);
178
+ const keys = picklistMemberKeys(values, mode);
179
+ const doc = `/** Active values of ${escapeComment(`${sobject}.${field.name}`)} (${escapeComment(field.type)}). */`;
180
+ if (mode === "enum") {
181
+ const members = values.map((value, index) => `\t${propertyKey(keys[index])} = ${JSON.stringify(value)},`);
182
+ return [
183
+ doc,
184
+ `export enum ${name} {`,
185
+ ...members,
186
+ "}"
187
+ ].join("\n");
188
+ }
189
+ const members = values.map((value, index) => `\t${propertyKey(keys[index])}: ${JSON.stringify(value)},`);
190
+ return [
191
+ doc,
192
+ `export const ${name} = {`,
193
+ ...members,
194
+ "} as const;",
195
+ `export type ${name} = (typeof ${name})[keyof typeof ${name}];`
196
+ ].join("\n");
53
197
  }
54
198
  //#endregion
55
199
  //#region src/codegen/generator.ts
@@ -60,24 +204,28 @@ const FIELD_KINDS = /* @__PURE__ */ new Set([
60
204
  "time",
61
205
  "multipicklist"
62
206
  ]);
63
- const IDENTIFIER$1 = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
64
207
  /**
65
208
  * Generates the TypeScript source for a set of sObject describes: a read interface, create
66
- * and update input types per sObject, and the `SObjectRegistry` that ties them together.
209
+ * and update input types per sObject, and the `SObjectRegistry` that ties them together. With
210
+ * picklist mode `"const"` or `"enum"`, each picklist also gets a named type before its sObject.
67
211
  * The output is deterministic: sObjects are sorted by name and fields by name (`Id` first).
68
212
  */
69
213
  function generateSource(describes, options = {}) {
70
214
  const sorted = [...describes].sort((a, b) => compare(a.name, b.name));
71
- assertGeneratableNames(sorted);
215
+ const definedNames = assertGeneratableNames(sorted);
72
216
  const names = new Set(sorted.map((describe) => describe.name));
217
+ const picklists = options.picklists ?? "union";
218
+ const picklistTypes = isNamedPicklistMode(picklists) ? picklistTypeNames(sorted, definedNames) : void 0;
73
219
  const sections = [imports(sorted, options.importSource ?? "@cerios/salesforce-sobjectly")];
74
220
  const objectHashes = [];
75
221
  for (const describe of sorted) {
222
+ const typeNames = picklistTypes?.get(describe.name);
76
223
  const objectSections = [
77
- readInterface(describe, options.picklists ?? "union"),
224
+ isNamedPicklistMode(picklists) ? picklistDeclarations(describe, typeNames, picklists) : "",
225
+ readInterface(describe, picklists, typeNames),
78
226
  inputType(describe, "CreateInput", "createable", lookup(options.excludeCreateFields, describe.name)),
79
227
  inputType(describe, "UpdateInput", "updateable", lookup(options.excludeUpdateFields, describe.name))
80
- ];
228
+ ].filter((section) => section.length > 0);
81
229
  sections.push(...objectSections);
82
230
  const fingerprint = [
83
231
  ...objectSections,
@@ -104,7 +252,10 @@ const RESERVED_TYPE_NAMES = /* @__PURE__ */ new Set([
104
252
  ADDRESS_TYPE,
105
253
  GEOLOCATION_TYPE
106
254
  ]);
107
- /** Fails with a clear message for sObject names the generated TypeScript can't represent. */
255
+ /**
256
+ * Fails with a clear message for sObject names the generated TypeScript can't represent. Returns
257
+ * every name the file defines apart from picklist types, so those can avoid them.
258
+ */
108
259
  function assertGeneratableNames(describes) {
109
260
  const seen = /* @__PURE__ */ new Map();
110
261
  const generated = new Set(describes.flatMap((describe) => [`${describe.name}CreateInput`, `${describe.name}UpdateInput`]));
@@ -115,6 +266,11 @@ function assertGeneratableNames(describes) {
115
266
  if (previous !== void 0) throw new Error(`sObjects "${previous}" and "${name}" differ only in case; list each sObject once.`);
116
267
  seen.set(name.toLowerCase(), name);
117
268
  }
269
+ return /* @__PURE__ */ new Set([
270
+ ...RESERVED_TYPE_NAMES,
271
+ ...generated,
272
+ ...describes.map((describe) => describe.name)
273
+ ]);
118
274
  }
119
275
  /** Looks up a per-sObject option case-insensitively (sObject names are case-insensitive in Salesforce). */
120
276
  function lookup(map, name) {
@@ -162,12 +318,11 @@ function imports(describes, importSource) {
162
318
  if (fields.some((field) => field.type === "location")) used.push(GEOLOCATION_TYPE);
163
319
  return used.length > 0 ? `import type { ${used.join(", ")} } from ${JSON.stringify(importSource)};` : "";
164
320
  }
165
- function sortedFields(describe) {
166
- return [...describe.fields].sort((a, b) => a.name === "Id" ? -1 : b.name === "Id" ? 1 : compare(a.name, b.name));
167
- }
168
- function readInterface(describe, picklists) {
321
+ function readInterface(describe, picklists, typeNames) {
169
322
  const lines = sortedFields(describe).map((field) => {
170
- return `${`\t/** ${escapeComment(field.label)} (${escapeComment(field.type)}) */`}\n\t${propertyKey(field.name)}: ${mapFieldType(field, picklists)};`;
323
+ const comment = `\t/** ${escapeComment(field.label)} (${escapeComment(field.type)}) */`;
324
+ const type = mapFieldType(field, picklists, typeNames?.get(field.name));
325
+ return `${comment}\n\t${propertyKey(field.name)}: ${type};`;
171
326
  });
172
327
  return `${describe.label ? `/** ${escapeComment(describe.label)} */\n` : ""}export interface ${describe.name} {\n${lines.join("\n")}\n}`;
173
328
  }
@@ -254,10 +409,6 @@ function recordTypeConstants(describes) {
254
409
  const entries = describes.map((describe) => [describe.name, recordTypeNames(describe)]).filter(([, names]) => names.length > 0).map(([name, names]) => `\t${name}: [${names.map((value) => JSON.stringify(value)).join(", ")}],`);
255
410
  return ["/** Record type DeveloperNames per sObject. Resolve ids with `sf.sobject(name).recordTypeId(developerName)`. */", `export const RECORD_TYPES = {${entries.length > 0 ? `\n${entries.join("\n")}\n` : ""}} as const;`].join("\n");
256
411
  }
257
- function activePicklistValues(field) {
258
- if (field.type !== "picklist" && field.type !== "multipicklist" && field.type !== "combobox") return [];
259
- return [...new Set((field.picklistValues ?? []).filter((entry) => entry.active).map((entry) => entry.value))];
260
- }
261
412
  function unionOrNever(values) {
262
413
  return values.length > 0 ? values.map((value) => JSON.stringify(value)).join(" | ") : "never";
263
414
  }
@@ -280,15 +431,6 @@ function relationshipMap(entries) {
280
431
  function keyUnion(names) {
281
432
  return names.map((name) => JSON.stringify(name)).join(" | ");
282
433
  }
283
- function propertyKey(name) {
284
- return IDENTIFIER$1.test(name) ? name : JSON.stringify(name);
285
- }
286
- function escapeComment(value) {
287
- return String(value ?? "").replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
288
- }
289
- function compare(a, b) {
290
- return a < b ? -1 : a > b ? 1 : 0;
291
- }
292
434
  //#endregion
293
435
  //#region src/codegen/validate-config.ts
294
436
  /** Thrown when a codegen config is invalid. Lists every problem found. */
@@ -319,7 +461,12 @@ const AUTH_OPTIONS = {
319
461
  ],
320
462
  sfCli: ["targetOrg"]
321
463
  };
322
- const PICKLIST_MODES = ["union", "string"];
464
+ const PICKLIST_MODES = [
465
+ "union",
466
+ "string",
467
+ "const",
468
+ "enum"
469
+ ];
323
470
  const IDENTIFIER = /^[A-Za-z][A-Za-z0-9_]*$/;
324
471
  const VALIDATORS = {
325
472
  $schema: (value, { problem }) => {
@@ -339,7 +486,7 @@ const VALIDATORS = {
339
486
  excludeCreateFields: (value, context) => validateFieldMap("excludeCreateFields", value, context),
340
487
  excludeUpdateFields: (value, context) => validateFieldMap("excludeUpdateFields", value, context),
341
488
  picklists: (value, { problem }) => {
342
- if (!PICKLIST_MODES.includes(value)) problem(`picklists: must be "union" or "string", got ${show(value)}.`);
489
+ if (!PICKLIST_MODES.includes(value)) problem(`picklists: must be one of ${PICKLIST_MODES.join(", ")}, got ${show(value)}.`);
343
490
  },
344
491
  constants: (value, { problem }) => {
345
492
  if (typeof value !== "boolean") problem(`constants: must be true or false, got ${show(value)}.`);
@@ -544,13 +691,24 @@ const AUTH_CHOICES = [
544
691
  label: "JWT bearer (certificate-based, runs as a user)"
545
692
  }
546
693
  ];
547
- const PICKLIST_CHOICES = [{
548
- value: "union",
549
- label: "Union of values, e.g. \"New\" | \"Closed\" (type-safe)"
550
- }, {
551
- value: "string",
552
- label: "Plain string"
553
- }];
694
+ const PICKLIST_CHOICES = [
695
+ {
696
+ value: "union",
697
+ label: "Union of values, e.g. \"New\" | \"Closed\" (type-safe)"
698
+ },
699
+ {
700
+ value: "string",
701
+ label: "Plain string"
702
+ },
703
+ {
704
+ value: "const",
705
+ label: "Union plus a named constant per picklist, e.g. CaseStatus.Closed"
706
+ },
707
+ {
708
+ value: "enum",
709
+ label: "TypeScript enum per picklist (restricted picklists only accept enum members)"
710
+ }
711
+ ];
554
712
  const FORMAT_CHOICES = [{
555
713
  value: "ts",
556
714
  label: "TypeScript (sobjectly.config.ts, type-checked with defineConfig)"
@@ -1115,6 +1273,12 @@ Object.defineProperty(exports, "FIELD_TYPE_MAP", {
1115
1273
  return FIELD_TYPE_MAP;
1116
1274
  }
1117
1275
  });
1276
+ Object.defineProperty(exports, "PICKLIST_MODES", {
1277
+ enumerable: true,
1278
+ get: function() {
1279
+ return PICKLIST_MODES;
1280
+ }
1281
+ });
1118
1282
  Object.defineProperty(exports, "checkGenerated", {
1119
1283
  enumerable: true,
1120
1284
  get: function() {
@@ -1206,4 +1370,4 @@ Object.defineProperty(exports, "validateConfig", {
1206
1370
  }
1207
1371
  });
1208
1372
 
1209
- //# sourceMappingURL=codegen-BgDgU2RR.cjs.map
1373
+ //# sourceMappingURL=codegen-Bylrt2Ju.cjs.map