@cerios/salesforce-sobjectly 0.0.0-stage → 1.0.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/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ The MIT License (MIT)
2
+ Copyright © 2026 Cerios
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,232 @@
1
- # Temporary Holding Version
1
+ # @cerios/salesforce-sobjectly
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A type-safe Salesforce REST API client for TypeScript. It generates types for your org's sObjects and gives you a typed SOQL builder, CRUD, composite requests, sObject collections, SOSL, invocable actions and Flows, the Tooling API and Bulk API 2.0. Requests go through native `fetch` by default, and any other HTTP client can be plugged in.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@cerios/salesforce-sobjectly.svg)](https://www.npmjs.com/package/@cerios/salesforce-sobjectly)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@cerios/salesforce-sobjectly.svg)](https://www.npmjs.com/package/@cerios/salesforce-sobjectly)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.4+-blue.svg)](https://www.typescriptlang.org/)
9
+
10
+ ## ✨ Key Features
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.
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
+ - **Relationship queries**: `selectRelated("Account", "Name")` and `selectChild("Contacts", c => c.select("Email"))` produce typed nested results.
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>()`.
16
+ - **Platform APIs**: platform events, approvals, quick actions, UI API, files, reports, query plans and Apex debug-log capture.
17
+ - **CI-friendly codegen**: `sobjectly generate --check` fails when the generated file is out of date and ignores formatting-only differences.
18
+ - **Bring your own HTTP client**: `fetch` is the default; any other client (axios, undici, Playwright's `APIRequestContext`, a logging wrapper) fits a one-method interface.
19
+ - **Built-in auth**: Salesforce CLI login (`sfCli`), static access token, client credentials, JWT bearer, refresh token or your own provider. Tokens are cached, and the client re-authenticates once on a 401.
20
+ - **Safe by default**: values are escaped in SOQL, absolute URLs are limited to your instance's origin, tokens are never logged, and retries are opt-in. Mutations are only retried when you ask for it, e.g. on `UNABLE_TO_LOCK_ROW`, where Salesforce rolled the request back.
21
+ - **Small footprint**: dual ESM/CJS builds. The only runtime dependency is `jiti`, which the codegen CLI uses to load TypeScript configs; the client never loads it.
22
+
23
+ ## 📦 Installation
24
+
25
+ ```bash
26
+ npm install @cerios/salesforce-sobjectly
27
+ ```
28
+
29
+ It needs Node.js 20.19 or later, and TypeScript 5.4 or later for the types.
30
+
31
+ ## 🎯 Quick Start
32
+
33
+ ### 1. Create a config
34
+
35
+ ```bash
36
+ npx sobjectly init
37
+ ```
38
+
39
+ `init` asks a few questions and writes `sobjectly.config.ts` or `sobjectly.config.json`:
40
+
41
+ - the API version, e.g. `v66.0`;
42
+ - where to write the generated types;
43
+ - which sObjects to generate;
44
+ - how to log in;
45
+ - how to type picklists;
46
+ - the config format: TypeScript (type-checked with `defineConfig`) or JSON (with a bundled JSON Schema for editor autocomplete).
47
+
48
+ It can also add the credential variables to `.env.example` and an npm script to `package.json`. Both formats are validated when loaded, and all problems are reported at once. A TypeScript config looks like this:
49
+
50
+ ```ts
51
+ import { defineConfig } from "@cerios/salesforce-sobjectly/codegen";
52
+
53
+ export default defineConfig({
54
+ apiVersion: "v66.0",
55
+ output: "src/generated/sobjects.ts",
56
+ sobjects: ["Account", "Contact", "Case", "User"],
57
+ // Reads SF_LOGIN_URL, SF_CLIENT_ID and SF_CLIENT_SECRET from the environment.
58
+ auth: { type: "clientCredentials" },
59
+ picklists: "union",
60
+ });
61
+ ```
62
+
63
+ ### 2. Generate types for your org
64
+
65
+ ```bash
66
+ npx sobjectly generate --env-file .env
67
+ ```
68
+
69
+ ### 3. Use the typed client
70
+
71
+ ```ts
72
+ import { SalesforceClient, clientCredentials } from "@cerios/salesforce-sobjectly";
73
+ import { API_VERSION, type SObjectRegistry } from "./generated/sobjects";
74
+
75
+ const sf = new SalesforceClient<SObjectRegistry>({
76
+ apiVersion: API_VERSION, // the same version the types were generated with
77
+ auth: clientCredentials({
78
+ loginUrl: process.env.SF_LOGIN_URL!, // your My Domain URL
79
+ clientId: process.env.SF_CLIENT_ID!,
80
+ clientSecret: process.env.SF_CLIENT_SECRET!,
81
+ }),
82
+ });
83
+
84
+ // CRUD
85
+ const accountId = await sf.sobject("Account").create({ Name: "Acme" });
86
+ await sf.sobject("Account").update(accountId, { Phone: "+31 20 123 4567" });
87
+ const account = await sf.sobject("Account").get(accountId, ["Id", "Name", "Phone"]);
88
+
89
+ // Typed SOQL
90
+ const page = await sf.query(
91
+ sf
92
+ .soql("Contact")
93
+ .select("Id", "Email")
94
+ .selectRelated("Account", "Name")
95
+ .where("Email", "LIKE", "%@acme.com")
96
+ .orderBy("LastName")
97
+ .limit(50),
98
+ );
99
+ page.records[0].Account?.Name; // string
100
+ page.records[0].Phone; // ❌ compile error: Phone was not selected
101
+
102
+ // Every record, across all pages
103
+ for await (const contact of sf.iterate(sf.soql("Contact").select("Id", "Email"))) {
104
+ console.log(contact.Email);
105
+ }
106
+ ```
107
+
108
+ ## 📖 Documentation
109
+
110
+ | Guide | What's in it |
111
+ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
112
+ | [Authentication](docs/authentication.md) | Salesforce CLI, access tokens, client credentials, JWT bearer, refresh tokens, custom providers |
113
+ | [Code generation](docs/codegen.md) | Config reference, type mapping, required fields, picklists, CI usage |
114
+ | [SOQL builder](docs/soql.md) | Selecting, relationships, filters, aggregates, pagination, escaping |
115
+ | [Composite & collections](docs/composite-and-collections.md) | Composite with references, batch, tree, graph, sObject collections |
116
+ | [Approvals, files, reports & more](docs/platform.md) | Record types, picklists, platform events, approvals, quick actions, UI API, files, reports, query plans |
117
+ | [Bulk API 2.0](docs/bulk.md) | Ingest jobs, query jobs, CSV handling |
118
+ | [Transports](docs/transports.md) | Custom HTTP clients (axios and Playwright examples), streaming, hooks, retries, timeouts |
119
+ | [Endpoint coverage](docs/endpoints.md) | Which Salesforce REST resources have typed helpers |
120
+
121
+ ## 🧭 API Overview
122
+
123
+ ```ts
124
+ sf.sobject("Account"): basicInfo, describe, create, get, update, delete, upsert, getByExternalId,
125
+ getDeleted, getUpdated, getBlob, query, collect, iterate
126
+ sf.soql("Account") → SoqlQueryBuilder
127
+ sf.query / sf.queryMore / sf.iterate / sf.collect (queryAll via { includeDeleted: true })
128
+ sf.search: sosl, parameterized, suggestions
129
+ sf.composite: execute, batch, tree, graph
130
+ sf.collections: create, update, upsert, delete, retrieve (≤ 200 per call, or { chunk: true })
131
+ sf.actions: invokeFlow, invokeApex, invokeStandard, invokeCustom, list*/describe*
132
+ sf.tooling: query, sobject, executeAnonymous, runTestsSynchronous, runTestsAsynchronous, request
133
+ sf.bulk: ingest, createIngestJob, query, createQueryJob
134
+ sf.events: publish (platform events)
135
+ sf.approvals: list, submit, approve, reject, pending
136
+ sf.files: upload, download, newVersion, link
137
+ sf.reports: list, describe, run, runAsync, instance, waitForInstance, toRows
138
+ sf.uiApi: objectInfo, picklistValues, record, layout
139
+ sf.quickActions / sf.sobject(name).quickActions: list, describe, defaultValues, invoke
140
+ sf.sobject(name).recordTypeId(), .picklistValues(); sf.explain(query); sf.clearCache()
141
+ sf.tooling.debugLogs: capture, list, body (executeAnonymous(apex, { captureLog: true }))
142
+ sf.apexRest(), sf.request(), sf.limits(), sf.versions(), sf.describeGlobal(), sf.recordCount()
143
+ ```
144
+
145
+ ### Flows and invocable actions
146
+
147
+ ```ts
148
+ const [result] = await sf.actions.invokeFlow<{ accountId: string }, { caseId: string }>("Start_Onboarding", [
149
+ { accountId },
150
+ ]);
151
+ result.outputValues?.caseId;
152
+ ```
153
+
154
+ ### Composite with references
155
+
156
+ ```ts
157
+ const result = await sf.composite.execute(
158
+ (c) => {
159
+ const account = c.create("Account", { Name: "Acme" });
160
+ const contact = c.create("Contact", { LastName: "Doe", AccountId: account.ref("id") });
161
+ return { account, contact };
162
+ },
163
+ { allOrNone: true },
164
+ );
165
+ const contactId = result.get(result.refs.contact).id;
166
+ ```
167
+
168
+ ### Any other endpoint
169
+
170
+ ```ts
171
+ import type { OrgLimits } from "@cerios/salesforce-sobjectly";
172
+
173
+ const limits = await sf.request<OrgLimits>({ path: "/limits" });
174
+ const tooling = await sf.request({ path: "/tooling/query", query: { q: "SELECT Id FROM ApexClass" } });
175
+ const custom = await sf.apexRest<{ ok: boolean }>({ method: "POST", path: "/orders", body: { id: 1 } });
176
+ ```
177
+
178
+ `path` is relative to `/services/data/{apiVersion}`. Paths that start with `/services/` are used as-is, and absolute URLs must be on your instance's origin.
179
+
180
+ ## 🔢 API version
181
+
182
+ There is no default API version: you always choose one.
183
+
184
+ - **Codegen:** `apiVersion` is required in `sobjectly.config.ts`, and `sobjectly generate` fails without it.
185
+ - **Client:** `apiVersion` is required in `new SalesforceClient({ ... })`, both as a TypeScript type and at runtime.
186
+
187
+ A fixed version means a Salesforce release, or an update of this package, never silently changes the API contract you call. Salesforce freezes behaviour per API version.
188
+
189
+ The generated file exports the version it was generated with as `API_VERSION`. Pass that to the client, so your types and your requests always use the same version. To upgrade, change `apiVersion` in the config and run `sobjectly generate` again.
190
+
191
+ ## 🧯 Errors
192
+
193
+ | Error | When |
194
+ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
195
+ | `SalesforceError` | Any non-2xx response. Carries `status`, `errorCode`, `errors[]`, `body`, `limitInfo` and a path without the query string. |
196
+ | `SalesforceSaveError` | A 2xx response in which an item reported `success: false` (create, collections, actions). |
197
+ | `SalesforcePartialFailureError` | A call split into several requests (chunked collections, batched events) failed after earlier chunks succeeded. `completedResults` holds their results; `cause` is the original error. |
198
+ | `SalesforceAuthError` | The token request failed. Carries the OAuth `error` and `errorDescription`, never secrets. |
199
+ | `SalesforceBulkJobError` | A Bulk job ended as `Failed` or `Aborted`, or timed out. |
200
+ | `ApexExecutionError` | Anonymous Apex failed to compile or threw. |
201
+ | `DebugLogCaptureError` | The work passed to `sf.tooling.debugLogs.capture()` threw. `logs` holds the logs collected anyway; `cause` is the original error. |
202
+
203
+ Error codes are typed, with autocomplete for the common ones (`SalesforceErrorCode`). Check them with the guards:
204
+
205
+ ```ts
206
+ import { hasErrorCode, isSalesforceError } from "@cerios/salesforce-sobjectly";
207
+
208
+ try {
209
+ await sf.sobject("Account").update(id, { Name: "Acme" });
210
+ } catch (error) {
211
+ if (hasErrorCode(error, "UNABLE_TO_LOCK_ROW")) {
212
+ // retry later
213
+ } else if (isSalesforceError(error, "INSUFFICIENT_ACCESS_OR_READONLY")) {
214
+ // ...
215
+ }
216
+ }
217
+ ```
218
+
219
+ ## 🤝 Contributing
220
+
221
+ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md). Report security issues as described in [SECURITY.md](SECURITY.md).
222
+
223
+ ## 📄 License
224
+
225
+ MIT © Ronald Veth - Cerios
226
+
227
+ ## 🔗 Links
228
+
229
+ - [GitHub repository](https://github.com/CeriosTesting/salesforce-sobjectly)
230
+ - [npm package](https://www.npmjs.com/package/@cerios/salesforce-sobjectly)
231
+ - [Issue tracker](https://github.com/CeriosTesting/salesforce-sobjectly/issues)
232
+ - [Changelog](CHANGELOG.md)
package/dist/cli.cjs ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ const require_sf_cli = require("./sf-cli-DEVZQTR7.cjs");
3
+ const require_codegen = require("./codegen-BgDgU2RR.cjs");
4
+ let node_fs = require("node:fs");
5
+ let node_path = require("node:path");
6
+ let node_util = require("node:util");
7
+ //#region src/cli.ts
8
+ const HELP = `Usage: sobjectly <command> [options]
9
+
10
+ Commands:
11
+ init Create a sobjectly.config file by answering a few questions
12
+ generate Generate TypeScript types for Salesforce sObjects (default)
13
+
14
+ Options for generate:
15
+ -c, --config <path> Config file (default: sobjectly.config.ts or sobjectly.config.json)
16
+ -e, --env-file <path> Load environment variables from a .env file first
17
+ --check Don't write; exit with 1 when the generated file is out of date
18
+ (formatting-only differences don't count)
19
+ --stdout Print the generated source instead of writing it
20
+
21
+ Options for init (skip the matching questions; useful in scripts):
22
+ --api-version <v> Salesforce API version, e.g. v66.0
23
+ --output <path> Generated types file, e.g. src/generated/sobjects.ts
24
+ --sobjects <list> Comma-separated sObject API names
25
+ --auth <type> clientCredentials | sfCli | accessToken | jwtBearer
26
+ --target-org <alias> Salesforce CLI org alias (with --auth sfCli)
27
+ --picklists <mode> union | string
28
+ --format <format> ts | json
29
+ -y, --yes Use the suggested answer for every other question (needs --api-version)
30
+ -f, --force Replace an existing config without asking
31
+
32
+ -h, --help Show this help
33
+ `;
34
+ const options = {
35
+ config: {
36
+ type: "string",
37
+ short: "c"
38
+ },
39
+ "env-file": {
40
+ type: "string",
41
+ short: "e"
42
+ },
43
+ "api-version": { type: "string" },
44
+ output: { type: "string" },
45
+ sobjects: { type: "string" },
46
+ auth: { type: "string" },
47
+ picklists: { type: "string" },
48
+ format: { type: "string" },
49
+ check: { type: "boolean" },
50
+ stdout: { type: "boolean" },
51
+ "target-org": { type: "string" },
52
+ yes: {
53
+ type: "boolean",
54
+ short: "y"
55
+ },
56
+ force: {
57
+ type: "boolean",
58
+ short: "f"
59
+ },
60
+ help: {
61
+ type: "boolean",
62
+ short: "h"
63
+ }
64
+ };
65
+ async function main(argv) {
66
+ const { values, positionals } = (0, node_util.parseArgs)({
67
+ args: argv,
68
+ allowPositionals: true,
69
+ options
70
+ });
71
+ const command = positionals[0] ?? "generate";
72
+ if (values.help) {
73
+ console.log(HELP);
74
+ return 0;
75
+ }
76
+ if (command === "init") return init(values);
77
+ if (command === "generate") return runGenerate(values);
78
+ console.error(`Unknown command "${command}".\n\n${HELP}`);
79
+ return 1;
80
+ }
81
+ async function init(values) {
82
+ const preset = initPreset(values);
83
+ const prompter = require_codegen.readlinePrompter();
84
+ try {
85
+ await require_codegen.runInit({
86
+ cwd: process.cwd(),
87
+ prompter,
88
+ log: (message) => console.log(message),
89
+ preset,
90
+ yes: values.yes,
91
+ force: values.force
92
+ });
93
+ return 0;
94
+ } finally {
95
+ prompter.close();
96
+ }
97
+ }
98
+ function initPreset(values) {
99
+ const preset = {};
100
+ if (values["api-version"] !== void 0) {
101
+ const apiVersion = require_sf_cli.normalizeApiVersion(values["api-version"]);
102
+ if (!apiVersion) throw new Error(`Invalid --api-version "${values["api-version"]}". Expected a value like v66.0.`);
103
+ preset.apiVersion = apiVersion;
104
+ }
105
+ if (values.output !== void 0) preset.output = values.output;
106
+ if (values.sobjects !== void 0) preset.sobjects = require_codegen.parseSObjects(values.sobjects);
107
+ const auth = oneOf("--auth", values.auth, [
108
+ "clientCredentials",
109
+ "sfCli",
110
+ "accessToken",
111
+ "jwtBearer"
112
+ ]);
113
+ if (values["target-org"] !== void 0) preset.targetOrg = values["target-org"];
114
+ const picklists = oneOf("--picklists", values.picklists, ["union", "string"]);
115
+ const format = oneOf("--format", values.format, ["ts", "json"]);
116
+ return {
117
+ ...preset,
118
+ ...auth && { auth },
119
+ ...picklists && { picklists },
120
+ ...format && { format }
121
+ };
122
+ }
123
+ function oneOf(flag, value, allowed) {
124
+ if (value === void 0) return;
125
+ const match = allowed.find((item) => item === value);
126
+ if (!match) throw new Error(`Invalid ${flag} "${value}". Expected one of: ${allowed.join(", ")}.`);
127
+ return match;
128
+ }
129
+ async function runGenerate(values) {
130
+ if (values["env-file"]) {
131
+ const envFile = (0, node_path.resolve)(values["env-file"]);
132
+ if (!(0, node_fs.existsSync)(envFile)) {
133
+ console.error(`Env file not found: ${envFile}`);
134
+ return 1;
135
+ }
136
+ process.loadEnvFile(envFile);
137
+ }
138
+ const found = require_codegen.findConfigFiles();
139
+ if (!values.config && found.length > 1) console.warn(`warning: found ${found.join(" and ")}; using ${found[0]}. Remove the other one.`);
140
+ const configPath = values.config ? (0, node_path.resolve)(values.config) : found[0];
141
+ if (!configPath) {
142
+ console.error("No sobjectly config file found. Run \"npx sobjectly init\" to create one, or pass --config.");
143
+ return 1;
144
+ }
145
+ const { config, directory } = await require_codegen.loadConfig(configPath);
146
+ const logger = {
147
+ info: values.stdout ? (message) => console.error(message) : (message) => console.log(message),
148
+ warn: (message) => console.warn(`warning: ${message}`)
149
+ };
150
+ if (values.check) return reportCheck(await require_codegen.checkGenerated(config, {
151
+ cwd: directory,
152
+ logger
153
+ }));
154
+ const result = await require_codegen.generate(config, {
155
+ cwd: directory,
156
+ logger,
157
+ write: !values.stdout
158
+ });
159
+ if (values.stdout) process.stdout.write(result.source);
160
+ if (result.skipped.length > 0) console.warn(`Skipped ${result.skipped.length} sObject(s): ${result.skipped.map((item) => item.name).join(", ")}`);
161
+ return 0;
162
+ }
163
+ function reportCheck(result) {
164
+ if (result.upToDate) {
165
+ console.log(`${result.outputPath} is up to date.`);
166
+ return 0;
167
+ }
168
+ if (result.reason === "missing") {
169
+ console.error(`${result.outputPath} does not exist. Run "sobjectly generate".`);
170
+ return 1;
171
+ }
172
+ const details = [
173
+ result.added.length > 0 ? `added: ${result.added.join(", ")}` : "",
174
+ result.removed.length > 0 ? `removed: ${result.removed.join(", ")}` : "",
175
+ result.changed.length > 0 ? `changed: ${result.changed.join(", ")}` : ""
176
+ ].filter((detail) => detail.length > 0);
177
+ console.error(`${result.outputPath} is out of date${details.length > 0 ? ` (${details.join("; ")})` : ""}. Run "sobjectly generate".`);
178
+ return 1;
179
+ }
180
+ main(process.argv.slice(2)).then((code) => {
181
+ process.exitCode = code;
182
+ }).catch((error) => {
183
+ console.error(error instanceof Error ? error.message : error);
184
+ process.exitCode = 1;
185
+ });
186
+ //#endregion
187
+
188
+ //# sourceMappingURL=cli.cjs.map
@@ -0,0 +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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"}
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.mjs ADDED
@@ -0,0 +1,196 @@
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";
4
+ import { existsSync } from "node:fs";
5
+ import { resolve } from "node:path";
6
+ import { parseArgs } from "node:util";
7
+ //#region src/cli.ts
8
+ var require_cli = /* @__PURE__ */ __commonJSMin((() => {
9
+ init_api_version();
10
+ init_codegen();
11
+ init_init();
12
+ init_prompter();
13
+ const HELP = `Usage: sobjectly <command> [options]
14
+
15
+ Commands:
16
+ init Create a sobjectly.config file by answering a few questions
17
+ generate Generate TypeScript types for Salesforce sObjects (default)
18
+
19
+ Options for generate:
20
+ -c, --config <path> Config file (default: sobjectly.config.ts or sobjectly.config.json)
21
+ -e, --env-file <path> Load environment variables from a .env file first
22
+ --check Don't write; exit with 1 when the generated file is out of date
23
+ (formatting-only differences don't count)
24
+ --stdout Print the generated source instead of writing it
25
+
26
+ Options for init (skip the matching questions; useful in scripts):
27
+ --api-version <v> Salesforce API version, e.g. v66.0
28
+ --output <path> Generated types file, e.g. src/generated/sobjects.ts
29
+ --sobjects <list> Comma-separated sObject API names
30
+ --auth <type> clientCredentials | sfCli | accessToken | jwtBearer
31
+ --target-org <alias> Salesforce CLI org alias (with --auth sfCli)
32
+ --picklists <mode> union | string
33
+ --format <format> ts | json
34
+ -y, --yes Use the suggested answer for every other question (needs --api-version)
35
+ -f, --force Replace an existing config without asking
36
+
37
+ -h, --help Show this help
38
+ `;
39
+ const options = {
40
+ config: {
41
+ type: "string",
42
+ short: "c"
43
+ },
44
+ "env-file": {
45
+ type: "string",
46
+ short: "e"
47
+ },
48
+ "api-version": { type: "string" },
49
+ output: { type: "string" },
50
+ sobjects: { type: "string" },
51
+ auth: { type: "string" },
52
+ picklists: { type: "string" },
53
+ format: { type: "string" },
54
+ check: { type: "boolean" },
55
+ stdout: { type: "boolean" },
56
+ "target-org": { type: "string" },
57
+ yes: {
58
+ type: "boolean",
59
+ short: "y"
60
+ },
61
+ force: {
62
+ type: "boolean",
63
+ short: "f"
64
+ },
65
+ help: {
66
+ type: "boolean",
67
+ short: "h"
68
+ }
69
+ };
70
+ async function main(argv) {
71
+ const { values, positionals } = parseArgs({
72
+ args: argv,
73
+ allowPositionals: true,
74
+ options
75
+ });
76
+ const command = positionals[0] ?? "generate";
77
+ if (values.help) {
78
+ console.log(HELP);
79
+ return 0;
80
+ }
81
+ if (command === "init") return init(values);
82
+ if (command === "generate") return runGenerate(values);
83
+ console.error(`Unknown command "${command}".\n\n${HELP}`);
84
+ return 1;
85
+ }
86
+ async function init(values) {
87
+ const preset = initPreset(values);
88
+ const prompter = readlinePrompter();
89
+ try {
90
+ await runInit({
91
+ cwd: process.cwd(),
92
+ prompter,
93
+ log: (message) => console.log(message),
94
+ preset,
95
+ yes: values.yes,
96
+ force: values.force
97
+ });
98
+ return 0;
99
+ } finally {
100
+ prompter.close();
101
+ }
102
+ }
103
+ function initPreset(values) {
104
+ const preset = {};
105
+ if (values["api-version"] !== void 0) {
106
+ const apiVersion = normalizeApiVersion(values["api-version"]);
107
+ if (!apiVersion) throw new Error(`Invalid --api-version "${values["api-version"]}". Expected a value like v66.0.`);
108
+ preset.apiVersion = apiVersion;
109
+ }
110
+ if (values.output !== void 0) preset.output = values.output;
111
+ if (values.sobjects !== void 0) preset.sobjects = parseSObjects(values.sobjects);
112
+ const auth = oneOf("--auth", values.auth, [
113
+ "clientCredentials",
114
+ "sfCli",
115
+ "accessToken",
116
+ "jwtBearer"
117
+ ]);
118
+ if (values["target-org"] !== void 0) preset.targetOrg = values["target-org"];
119
+ const picklists = oneOf("--picklists", values.picklists, ["union", "string"]);
120
+ const format = oneOf("--format", values.format, ["ts", "json"]);
121
+ return {
122
+ ...preset,
123
+ ...auth && { auth },
124
+ ...picklists && { picklists },
125
+ ...format && { format }
126
+ };
127
+ }
128
+ function oneOf(flag, value, allowed) {
129
+ if (value === void 0) return;
130
+ const match = allowed.find((item) => item === value);
131
+ if (!match) throw new Error(`Invalid ${flag} "${value}". Expected one of: ${allowed.join(", ")}.`);
132
+ return match;
133
+ }
134
+ async function runGenerate(values) {
135
+ if (values["env-file"]) {
136
+ const envFile = resolve(values["env-file"]);
137
+ if (!existsSync(envFile)) {
138
+ console.error(`Env file not found: ${envFile}`);
139
+ return 1;
140
+ }
141
+ process.loadEnvFile(envFile);
142
+ }
143
+ const found = findConfigFiles();
144
+ if (!values.config && found.length > 1) console.warn(`warning: found ${found.join(" and ")}; using ${found[0]}. Remove the other one.`);
145
+ const configPath = values.config ? resolve(values.config) : found[0];
146
+ if (!configPath) {
147
+ console.error("No sobjectly config file found. Run \"npx sobjectly init\" to create one, or pass --config.");
148
+ return 1;
149
+ }
150
+ const { config, directory } = await loadConfig(configPath);
151
+ const logger = {
152
+ info: values.stdout ? (message) => console.error(message) : (message) => console.log(message),
153
+ warn: (message) => console.warn(`warning: ${message}`)
154
+ };
155
+ if (values.check) return reportCheck(await checkGenerated(config, {
156
+ cwd: directory,
157
+ logger
158
+ }));
159
+ const result = await generate(config, {
160
+ cwd: directory,
161
+ logger,
162
+ write: !values.stdout
163
+ });
164
+ if (values.stdout) process.stdout.write(result.source);
165
+ if (result.skipped.length > 0) console.warn(`Skipped ${result.skipped.length} sObject(s): ${result.skipped.map((item) => item.name).join(", ")}`);
166
+ return 0;
167
+ }
168
+ function reportCheck(result) {
169
+ if (result.upToDate) {
170
+ console.log(`${result.outputPath} is up to date.`);
171
+ return 0;
172
+ }
173
+ if (result.reason === "missing") {
174
+ console.error(`${result.outputPath} does not exist. Run "sobjectly generate".`);
175
+ return 1;
176
+ }
177
+ const details = [
178
+ result.added.length > 0 ? `added: ${result.added.join(", ")}` : "",
179
+ result.removed.length > 0 ? `removed: ${result.removed.join(", ")}` : "",
180
+ result.changed.length > 0 ? `changed: ${result.changed.join(", ")}` : ""
181
+ ].filter((detail) => detail.length > 0);
182
+ console.error(`${result.outputPath} is out of date${details.length > 0 ? ` (${details.join("; ")})` : ""}. Run "sobjectly generate".`);
183
+ return 1;
184
+ }
185
+ main(process.argv.slice(2)).then((code) => {
186
+ process.exitCode = code;
187
+ }).catch((error) => {
188
+ console.error(error instanceof Error ? error.message : error);
189
+ process.exitCode = 1;
190
+ });
191
+ }));
192
+ //#endregion
193
+ export default require_cli();
194
+ export {};
195
+
196
+ //# sourceMappingURL=cli.mjs.map