@cyberxon/xon 0.2.1 → 0.3.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/dist/cli.js +241 -61
- package/dist/deploy.js +36 -2
- package/dist/flags-dir.js +35 -0
- package/dist/org.js +12 -0
- package/dist/project.js +13 -0
- package/dist/retrieve.js +14 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { Command, CommanderError } from "commander";
|
|
4
6
|
import { ApiClient, ApiError } from "./api-client.js";
|
|
5
|
-
import { createProject, readProject } from "./project.js";
|
|
7
|
+
import { createProject, readProject, tryReadProject, updateProjectConfig } from "./project.js";
|
|
6
8
|
import { updateCli } from "./update.js";
|
|
7
|
-
import { cancelDeploy, previewDeploy, quickDeploy, reportDeploy, resumeDeploy, startDeploy } from "./deploy.js";
|
|
9
|
+
import { cancelDeploy, COVERAGE_FORMATTERS, previewDeploy, quickDeploy, reportDeploy, resumeDeploy, startDeploy, TEST_LEVELS, } from "./deploy.js";
|
|
8
10
|
import { previewRetrieve, resumeRetrieve, startRetrieve } from "./retrieve.js";
|
|
9
11
|
import { collectSourceFiles, writeSourceFiles } from "./source.js";
|
|
10
|
-
import { displayOrg, loginAccessToken, loginJwt, loginSfdxUrl, loginWeb, logoutOrg, openInBrowser, openOrg } from "./org.js";
|
|
12
|
+
import { displayOrg, loginAccessToken, loginCredentials, loginJwt, loginSfdxUrl, loginWeb, logoutOrg, openInBrowser, openOrg } from "./org.js";
|
|
11
13
|
import { clearOrgs, listOrgs } from "./org-store.js";
|
|
14
|
+
import { applyFlagsDir } from "./flags-dir.js";
|
|
15
|
+
const cliVersion = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
12
16
|
const program = new Command();
|
|
13
17
|
function output(value) {
|
|
14
18
|
console.log(typeof value === "string" ? value : JSON.stringify(value, null, 2));
|
|
@@ -20,10 +24,11 @@ async function readStdin() {
|
|
|
20
24
|
}
|
|
21
25
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
22
26
|
}
|
|
23
|
-
function apiClient(options) {
|
|
24
|
-
const
|
|
27
|
+
async function apiClient(options) {
|
|
28
|
+
const project = await tryReadProject(".");
|
|
29
|
+
const baseUrl = options.baseUrl ?? process.env.XON_BASE_URL ?? project?.baseUrl;
|
|
25
30
|
if (!baseUrl) {
|
|
26
|
-
throw new Error("An API base URL is required. Set XON_BASE_URL
|
|
31
|
+
throw new Error("An API base URL is required. Set XON_BASE_URL, pass --base-url, or run 'xon org login' inside a project to save one.");
|
|
27
32
|
}
|
|
28
33
|
return new ApiClient({ baseUrl, token: options.token ?? process.env.XON_TOKEN });
|
|
29
34
|
}
|
|
@@ -35,6 +40,21 @@ async function requireProjectRoot() {
|
|
|
35
40
|
throw new Error("Not in a xon project directory (missing .xon/project.json). Run this inside a project created with 'xon project create'.");
|
|
36
41
|
}
|
|
37
42
|
}
|
|
43
|
+
async function resolveTargetOrg(explicit) {
|
|
44
|
+
if (explicit) {
|
|
45
|
+
return explicit;
|
|
46
|
+
}
|
|
47
|
+
const project = await tryReadProject(".");
|
|
48
|
+
return project?.defaultOrg;
|
|
49
|
+
}
|
|
50
|
+
async function saveProjectLoginDefaults(baseUrl, orgAlias) {
|
|
51
|
+
const project = await tryReadProject(".");
|
|
52
|
+
if (!project) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await updateProjectConfig(".", { baseUrl, defaultOrg: orgAlias });
|
|
56
|
+
console.log(`Saved base-url and default org to .xon/project.json`);
|
|
57
|
+
}
|
|
38
58
|
async function waitForDeploy(client, status, waitSeconds) {
|
|
39
59
|
if (waitSeconds > 0 && !status.done) {
|
|
40
60
|
return resumeDeploy(client, status.id, waitSeconds);
|
|
@@ -48,10 +68,72 @@ const parseWait = (value) => {
|
|
|
48
68
|
}
|
|
49
69
|
return parsed;
|
|
50
70
|
};
|
|
71
|
+
const DEFAULT_DEPLOY_WAIT_SECONDS = 33 * 60;
|
|
72
|
+
const collect = (value, previous) => previous.concat([value]);
|
|
73
|
+
function parseEnum(name, allowed) {
|
|
74
|
+
return (value) => {
|
|
75
|
+
if (!allowed.includes(value)) {
|
|
76
|
+
throw new Error(`${name} must be one of: ${allowed.join(", ")}`);
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const parseTestLevel = parseEnum("--test-level", TEST_LEVELS);
|
|
82
|
+
const parseCoverageFormatter = (value, previous) => {
|
|
83
|
+
if (!COVERAGE_FORMATTERS.includes(value)) {
|
|
84
|
+
throw new Error(`--coverage-formatters must be one of: ${COVERAGE_FORMATTERS.join(", ")}`);
|
|
85
|
+
}
|
|
86
|
+
return previous.concat([value]);
|
|
87
|
+
};
|
|
88
|
+
async function writeDeployResults(resultsDir, status) {
|
|
89
|
+
if (!resultsDir) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const dir = path.resolve(resultsDir, status.id);
|
|
93
|
+
await mkdir(dir, { recursive: true });
|
|
94
|
+
await writeFile(path.join(dir, "deploy-result.json"), `${JSON.stringify(status, null, 2)}\n`);
|
|
95
|
+
console.log(`Wrote results to ${dir}`);
|
|
96
|
+
}
|
|
97
|
+
function printDeployResult(status, options) {
|
|
98
|
+
if (options.concise && !options.json) {
|
|
99
|
+
const { id, status: state, success, numberComponentsDeployed, numberComponentsTotal } = status;
|
|
100
|
+
console.log(`${id} ${state}${success === undefined ? "" : ` success=${success}`}${numberComponentsDeployed === undefined ? "" : ` (${numberComponentsDeployed}/${numberComponentsTotal})`}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
output(status);
|
|
104
|
+
}
|
|
105
|
+
function addDeployStartFlags(cmd) {
|
|
106
|
+
return cmd
|
|
107
|
+
.option("-a, --api-version <version>", "Target API version for the deploy")
|
|
108
|
+
.option("--concise", "Display simplified deployment results output", false)
|
|
109
|
+
.option("--coverage-formatters <formatter>", "Format of the code coverage results (repeatable)", parseCoverageFormatter, [])
|
|
110
|
+
.option("--flags-dir <path>", "Import flag values from a directory")
|
|
111
|
+
.option("-c, --ignore-conflicts", "Ignore conflicts and deploy local files, even if they overwrite changes in the org", false)
|
|
112
|
+
.option("-r, --ignore-errors", "Ignore any errors and don't roll back deployment", false)
|
|
113
|
+
.option("-g, --ignore-warnings", "Ignore warnings and allow a deployment to complete successfully", false)
|
|
114
|
+
.option("--json", "Format output as JSON", false)
|
|
115
|
+
.option("--junit", "Output JUnit test results", false)
|
|
116
|
+
.option("-x, --manifest <path>", "Full file path for manifest (package.xml) of components to deploy")
|
|
117
|
+
.option("-m, --metadata <name>", "Metadata component names to deploy (repeatable)", collect, [])
|
|
118
|
+
.option("--metadata-dir <path>", "Root of directory of metadata formatted files to deploy")
|
|
119
|
+
.option("--post-destructive-changes <path>", "File path for a manifest of components to delete after the deploy")
|
|
120
|
+
.option("--pre-destructive-changes <path>", "File path for a manifest of components to delete before the deploy")
|
|
121
|
+
.option("--purge-on-delete", "Immediately eligible for deletion rather than stored in the Recycle Bin", false)
|
|
122
|
+
.option("--results-dir <path>", "Output directory for code coverage and JUnit results")
|
|
123
|
+
.option("--single-package", "Indicates that the metadata zip file points to a directory structure for a single package", false)
|
|
124
|
+
.option("-d, --source-dir <path>", "Path to the local source files to deploy (repeatable)", collect, [])
|
|
125
|
+
.option("-o, --target-org <org>", "Username or alias of the target org")
|
|
126
|
+
.option("-l, --test-level <level>", `Deployment Apex testing level (${TEST_LEVELS.join(", ")})`, parseTestLevel)
|
|
127
|
+
.option("-t, --tests <name>", "Apex tests to run when --test-level is RunSpecifiedTests (repeatable)", collect, [])
|
|
128
|
+
.option("--verbose", "Show detailed deployment results output", false)
|
|
129
|
+
.option("-w, --wait <seconds>", "Seconds to wait for the deploy to finish (0 = don't wait)", parseWait, DEFAULT_DEPLOY_WAIT_SECONDS)
|
|
130
|
+
.option("--base-url <url>", "API base URL")
|
|
131
|
+
.option("--token <token>", "Bearer token");
|
|
132
|
+
}
|
|
51
133
|
program
|
|
52
134
|
.name("xon")
|
|
53
135
|
.description("Manage Xon projects and deployments")
|
|
54
|
-
.version(
|
|
136
|
+
.version(cliVersion)
|
|
55
137
|
.showSuggestionAfterError();
|
|
56
138
|
const project = program.command("project").description("Manage Xon projects");
|
|
57
139
|
project
|
|
@@ -63,40 +145,72 @@ project
|
|
|
63
145
|
console.log(`Created project ${name} in ${root}`);
|
|
64
146
|
});
|
|
65
147
|
const projectDeploy = project.command("deploy").description("Deploy metadata between your project and an org");
|
|
66
|
-
projectDeploy
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
.
|
|
70
|
-
.option("-o, --target-org <org>", "Org to deploy to")
|
|
71
|
-
.option("-c, --dry-run", "Validate the deploy without applying it", false)
|
|
72
|
-
.option("-w, --wait <seconds>", "Seconds to wait for the deploy to finish (0 = don't wait)", parseWait, 0)
|
|
73
|
-
.option("-b, --base-url <url>", "API base URL")
|
|
74
|
-
.option("-t, --token <token>", "Bearer token")
|
|
75
|
-
.action(async (options) => {
|
|
148
|
+
addDeployStartFlags(projectDeploy.command("start").description("Deploy metadata to an org from your local project"))
|
|
149
|
+
.option("--async", "Run the command asynchronously and return the job ID immediately", false)
|
|
150
|
+
.option("--dry-run", "Validate deploy and run Apex tests but don't save to the org", false)
|
|
151
|
+
.action(async (options, command) => {
|
|
76
152
|
await requireProjectRoot();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
153
|
+
if (options.flagsDir) {
|
|
154
|
+
await applyFlagsDir(command, options.flagsDir);
|
|
155
|
+
options = command.opts();
|
|
156
|
+
}
|
|
157
|
+
const client = await apiClient(options);
|
|
158
|
+
const sourceDirs = (options.sourceDir.length > 0 ? options.sourceDir : ["src"]).map((dir) => path.resolve(dir));
|
|
159
|
+
const status = await startDeploy(client, sourceDirs, {
|
|
160
|
+
targetOrg: await resolveTargetOrg(options.targetOrg),
|
|
80
161
|
checkOnly: options.dryRun,
|
|
162
|
+
apiVersion: options.apiVersion,
|
|
163
|
+
manifest: options.manifest,
|
|
164
|
+
metadata: options.metadata && options.metadata.length > 0 ? options.metadata : undefined,
|
|
165
|
+
metadataDir: options.metadataDir,
|
|
166
|
+
singlePackage: options.singlePackage,
|
|
167
|
+
testLevel: options.testLevel,
|
|
168
|
+
tests: options.tests && options.tests.length > 0 ? options.tests : undefined,
|
|
169
|
+
ignoreErrors: options.ignoreErrors,
|
|
170
|
+
ignoreWarnings: options.ignoreWarnings,
|
|
171
|
+
ignoreConflicts: options.ignoreConflicts,
|
|
172
|
+
purgeOnDelete: options.purgeOnDelete,
|
|
173
|
+
preDestructiveChanges: options.preDestructiveChanges,
|
|
174
|
+
postDestructiveChanges: options.postDestructiveChanges,
|
|
175
|
+
coverageFormatters: options.coverageFormatters && options.coverageFormatters.length > 0 ? options.coverageFormatters : undefined,
|
|
176
|
+
junit: options.junit,
|
|
81
177
|
});
|
|
82
|
-
|
|
178
|
+
const waitSeconds = options.async ? 0 : options.wait;
|
|
179
|
+
const final = await waitForDeploy(client, status, waitSeconds);
|
|
180
|
+
await writeDeployResults(options.resultsDir, final);
|
|
181
|
+
printDeployResult(final, options);
|
|
83
182
|
});
|
|
84
|
-
projectDeploy
|
|
85
|
-
.command
|
|
86
|
-
.description("Validate a metadata deployment without actually executing it")
|
|
87
|
-
.option("-d, --source-dir <dir>", "Directory of source to validate", "src")
|
|
88
|
-
.option("-o, --target-org <org>", "Org to validate against")
|
|
89
|
-
.option("-w, --wait <seconds>", "Seconds to wait for validation to finish (0 = don't wait)", parseWait, 0)
|
|
90
|
-
.option("-b, --base-url <url>", "API base URL")
|
|
91
|
-
.option("-t, --token <token>", "Bearer token")
|
|
92
|
-
.action(async (options) => {
|
|
183
|
+
addDeployStartFlags(projectDeploy.command("validate").description("Validate a metadata deployment without actually executing it"))
|
|
184
|
+
.action(async (options, command) => {
|
|
93
185
|
await requireProjectRoot();
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
186
|
+
if (options.flagsDir) {
|
|
187
|
+
await applyFlagsDir(command, options.flagsDir);
|
|
188
|
+
options = command.opts();
|
|
189
|
+
}
|
|
190
|
+
const client = await apiClient(options);
|
|
191
|
+
const sourceDirs = (options.sourceDir.length > 0 ? options.sourceDir : ["src"]).map((dir) => path.resolve(dir));
|
|
192
|
+
const status = await startDeploy(client, sourceDirs, {
|
|
193
|
+
targetOrg: await resolveTargetOrg(options.targetOrg),
|
|
97
194
|
checkOnly: true,
|
|
195
|
+
apiVersion: options.apiVersion,
|
|
196
|
+
manifest: options.manifest,
|
|
197
|
+
metadata: options.metadata && options.metadata.length > 0 ? options.metadata : undefined,
|
|
198
|
+
metadataDir: options.metadataDir,
|
|
199
|
+
singlePackage: options.singlePackage,
|
|
200
|
+
testLevel: options.testLevel,
|
|
201
|
+
tests: options.tests && options.tests.length > 0 ? options.tests : undefined,
|
|
202
|
+
ignoreErrors: options.ignoreErrors,
|
|
203
|
+
ignoreWarnings: options.ignoreWarnings,
|
|
204
|
+
ignoreConflicts: options.ignoreConflicts,
|
|
205
|
+
purgeOnDelete: options.purgeOnDelete,
|
|
206
|
+
preDestructiveChanges: options.preDestructiveChanges,
|
|
207
|
+
postDestructiveChanges: options.postDestructiveChanges,
|
|
208
|
+
coverageFormatters: options.coverageFormatters && options.coverageFormatters.length > 0 ? options.coverageFormatters : undefined,
|
|
209
|
+
junit: options.junit,
|
|
98
210
|
});
|
|
99
|
-
|
|
211
|
+
const final = await waitForDeploy(client, status, options.wait);
|
|
212
|
+
await writeDeployResults(options.resultsDir, final);
|
|
213
|
+
printDeployResult(final, options);
|
|
100
214
|
});
|
|
101
215
|
projectDeploy
|
|
102
216
|
.command("quick")
|
|
@@ -106,7 +220,7 @@ projectDeploy
|
|
|
106
220
|
.option("-b, --base-url <url>", "API base URL")
|
|
107
221
|
.option("-t, --token <token>", "Bearer token")
|
|
108
222
|
.action(async (options) => {
|
|
109
|
-
const client = apiClient(options);
|
|
223
|
+
const client = await apiClient(options);
|
|
110
224
|
const status = await quickDeploy(client, options.jobId);
|
|
111
225
|
output(await waitForDeploy(client, status, options.wait));
|
|
112
226
|
});
|
|
@@ -117,7 +231,7 @@ projectDeploy
|
|
|
117
231
|
.option("-b, --base-url <url>", "API base URL")
|
|
118
232
|
.option("-t, --token <token>", "Bearer token")
|
|
119
233
|
.action(async (options) => {
|
|
120
|
-
output(await reportDeploy(apiClient(options), options.jobId));
|
|
234
|
+
output(await reportDeploy(await apiClient(options), options.jobId));
|
|
121
235
|
});
|
|
122
236
|
projectDeploy
|
|
123
237
|
.command("resume")
|
|
@@ -127,7 +241,7 @@ projectDeploy
|
|
|
127
241
|
.option("-b, --base-url <url>", "API base URL")
|
|
128
242
|
.option("-t, --token <token>", "Bearer token")
|
|
129
243
|
.action(async (options) => {
|
|
130
|
-
output(await resumeDeploy(apiClient(options), options.jobId, options.wait));
|
|
244
|
+
output(await resumeDeploy(await apiClient(options), options.jobId, options.wait));
|
|
131
245
|
});
|
|
132
246
|
projectDeploy
|
|
133
247
|
.command("cancel")
|
|
@@ -136,7 +250,7 @@ projectDeploy
|
|
|
136
250
|
.option("-b, --base-url <url>", "API base URL")
|
|
137
251
|
.option("-t, --token <token>", "Bearer token")
|
|
138
252
|
.action(async (options) => {
|
|
139
|
-
output(await cancelDeploy(apiClient(options), options.jobId));
|
|
253
|
+
output(await cancelDeploy(await apiClient(options), options.jobId));
|
|
140
254
|
});
|
|
141
255
|
projectDeploy
|
|
142
256
|
.command("preview")
|
|
@@ -151,23 +265,56 @@ const projectRetrieve = project.command("retrieve").description("Retrieve metada
|
|
|
151
265
|
projectRetrieve
|
|
152
266
|
.command("start")
|
|
153
267
|
.description("Retrieve metadata from an org to your local project")
|
|
154
|
-
.option("-
|
|
155
|
-
.option("
|
|
156
|
-
.option("-
|
|
157
|
-
.option("
|
|
158
|
-
.option("-
|
|
159
|
-
.
|
|
268
|
+
.option("-a, --api-version <version>", "Target API version for the retrieve")
|
|
269
|
+
.option("--flags-dir <path>", "Import flag values from a directory")
|
|
270
|
+
.option("-c, --ignore-conflicts", "Ignore conflicts and save files, even if they overwrite local changes", false)
|
|
271
|
+
.option("--json", "Format output as JSON", false)
|
|
272
|
+
.option("-x, --manifest <path>", "File path for the manifest (package.xml) that specifies the components to retrieve")
|
|
273
|
+
.option("-m, --metadata <name>", "Metadata component names to retrieve (repeatable)", collect, [])
|
|
274
|
+
.option("-r, --output-dir <path>", "Directory root for the retrieved source files", "src")
|
|
275
|
+
.option("-n, --package-name <name>", "Package names to retrieve (repeatable)", collect, [])
|
|
276
|
+
.option("--single-package", "Indicates that the zip file points to a directory structure for a single package", false)
|
|
277
|
+
.option("-d, --source-dir <path>", "File paths for source to retrieve from the org (repeatable)", collect, [])
|
|
278
|
+
.option("-t, --target-metadata-dir <path>", "Directory to write the retrieved metadata-format zip into")
|
|
279
|
+
.option("-o, --target-org <org>", "Username or alias of the target org")
|
|
280
|
+
.option("-z, --unzip", "Extract files from the retrieved zip file", false)
|
|
281
|
+
.option("-w, --wait <seconds>", "Seconds to wait for the retrieve to finish", parseWait, DEFAULT_DEPLOY_WAIT_SECONDS)
|
|
282
|
+
.option("--zip-file-name <name>", "File name for the retrieved zip file")
|
|
283
|
+
.option("--base-url <url>", "API base URL")
|
|
284
|
+
.option("--token <token>", "Bearer token")
|
|
285
|
+
.action(async (options, command) => {
|
|
160
286
|
await requireProjectRoot();
|
|
161
|
-
|
|
162
|
-
|
|
287
|
+
if (options.flagsDir) {
|
|
288
|
+
await applyFlagsDir(command, options.flagsDir);
|
|
289
|
+
options = command.opts();
|
|
290
|
+
}
|
|
291
|
+
const client = await apiClient(options);
|
|
292
|
+
let result = await startRetrieve(client, {
|
|
293
|
+
targetOrg: await resolveTargetOrg(options.targetOrg),
|
|
294
|
+
apiVersion: options.apiVersion,
|
|
295
|
+
manifest: options.manifest,
|
|
296
|
+
metadata: options.metadata && options.metadata.length > 0 ? options.metadata : undefined,
|
|
297
|
+
packageNames: options.packageName && options.packageName.length > 0 ? options.packageName : undefined,
|
|
298
|
+
sourceDir: options.sourceDir.length > 0 ? options.sourceDir : undefined,
|
|
299
|
+
singlePackage: options.singlePackage,
|
|
300
|
+
ignoreConflicts: options.ignoreConflicts,
|
|
301
|
+
zipFileName: options.zipFileName,
|
|
302
|
+
});
|
|
163
303
|
if (options.wait > 0 && !result.done) {
|
|
164
304
|
result = await resumeRetrieve(client, result.id, options.wait);
|
|
165
305
|
}
|
|
166
|
-
if (result.success && result.files) {
|
|
167
|
-
await writeSourceFiles(path.resolve(options.
|
|
168
|
-
console.log(`Retrieved ${result.files.length} file(s) into ${options.
|
|
306
|
+
if (result.success && result.files && (options.unzip || !options.targetMetadataDir)) {
|
|
307
|
+
await writeSourceFiles(path.resolve(options.outputDir), result.files);
|
|
308
|
+
console.log(`Retrieved ${result.files.length} file(s) into ${options.outputDir}`);
|
|
169
309
|
}
|
|
170
|
-
|
|
310
|
+
if (result.success && result.metadataZip && options.targetMetadataDir) {
|
|
311
|
+
const zipDir = path.resolve(options.targetMetadataDir);
|
|
312
|
+
await mkdir(zipDir, { recursive: true });
|
|
313
|
+
const zipPath = path.join(zipDir, options.zipFileName ?? "unpackaged.zip");
|
|
314
|
+
await writeFile(zipPath, Buffer.from(result.metadataZip, "base64"));
|
|
315
|
+
console.log(`Wrote metadata zip to ${zipPath}`);
|
|
316
|
+
}
|
|
317
|
+
const { files, metadataZip, ...summary } = result;
|
|
171
318
|
output(summary);
|
|
172
319
|
});
|
|
173
320
|
projectRetrieve
|
|
@@ -177,7 +324,7 @@ projectRetrieve
|
|
|
177
324
|
.option("-b, --base-url <url>", "API base URL")
|
|
178
325
|
.option("-t, --token <token>", "Bearer token")
|
|
179
326
|
.action(async (options) => {
|
|
180
|
-
const rows = await previewRetrieve(apiClient(options), options.targetOrg);
|
|
327
|
+
const rows = await previewRetrieve(await apiClient(options), options.targetOrg);
|
|
181
328
|
console.table(rows);
|
|
182
329
|
});
|
|
183
330
|
const org = program.command("org").description("Manage your Salesforce org connections");
|
|
@@ -192,13 +339,15 @@ orgLogin
|
|
|
192
339
|
.option("-b, --base-url <url>", "API base URL")
|
|
193
340
|
.option("-t, --token <token>", "Bearer token")
|
|
194
341
|
.action(async (options) => {
|
|
195
|
-
const
|
|
342
|
+
const client = await apiClient(options);
|
|
343
|
+
const record = await loginWeb(client, {
|
|
196
344
|
alias: options.alias,
|
|
197
345
|
setDefault: options.setDefault,
|
|
198
346
|
waitSeconds: options.wait,
|
|
199
347
|
instanceUrl: options.instanceUrl,
|
|
200
348
|
});
|
|
201
349
|
console.log(`Logged in to ${record.instanceUrl} as ${record.username} (alias: ${record.alias})`);
|
|
350
|
+
await saveProjectLoginDefaults(client.baseUrl, record.alias);
|
|
202
351
|
});
|
|
203
352
|
orgLogin
|
|
204
353
|
.command("jwt")
|
|
@@ -212,8 +361,10 @@ orgLogin
|
|
|
212
361
|
.option("-b, --base-url <url>", "API base URL")
|
|
213
362
|
.option("-t, --token <token>", "Bearer token")
|
|
214
363
|
.action(async (options) => {
|
|
215
|
-
const
|
|
364
|
+
const client = await apiClient(options);
|
|
365
|
+
const record = await loginJwt(client, options);
|
|
216
366
|
console.log(`Logged in to ${record.instanceUrl} as ${record.username} (alias: ${record.alias})`);
|
|
367
|
+
await saveProjectLoginDefaults(client.baseUrl, record.alias);
|
|
217
368
|
});
|
|
218
369
|
orgLogin
|
|
219
370
|
.command("sfdx-url")
|
|
@@ -232,8 +383,10 @@ orgLogin
|
|
|
232
383
|
: (() => {
|
|
233
384
|
throw new Error("Pass --sfdx-url-file <path> or --sfdx-url-stdin.");
|
|
234
385
|
})();
|
|
235
|
-
const
|
|
386
|
+
const client = await apiClient(options);
|
|
387
|
+
const record = await loginSfdxUrl(client, { sfdxAuthUrl, alias: options.alias, setDefault: options.setDefault });
|
|
236
388
|
console.log(`Logged in to ${record.instanceUrl} as ${record.username} (alias: ${record.alias})`);
|
|
389
|
+
await saveProjectLoginDefaults(client.baseUrl, record.alias);
|
|
237
390
|
});
|
|
238
391
|
orgLogin
|
|
239
392
|
.command("access-token")
|
|
@@ -248,13 +401,40 @@ orgLogin
|
|
|
248
401
|
if (!accessToken) {
|
|
249
402
|
throw new Error("Pipe the access token via stdin, e.g. echo $TOKEN | xon org login access-token -r <url>.");
|
|
250
403
|
}
|
|
251
|
-
const
|
|
404
|
+
const client = await apiClient(options);
|
|
405
|
+
const record = await loginAccessToken(client, {
|
|
252
406
|
instanceUrl: options.instanceUrl,
|
|
253
407
|
accessToken,
|
|
254
408
|
alias: options.alias,
|
|
255
409
|
setDefault: options.setDefault,
|
|
256
410
|
});
|
|
257
411
|
console.log(`Logged in to ${record.instanceUrl} as ${record.username} (alias: ${record.alias})`);
|
|
412
|
+
await saveProjectLoginDefaults(client.baseUrl, record.alias);
|
|
413
|
+
});
|
|
414
|
+
orgLogin
|
|
415
|
+
.command("credentials")
|
|
416
|
+
.description("Log in using your xon backend credentials, exchanged for an org access token")
|
|
417
|
+
.requiredOption("-u, --login-id <id>", "Login id / username for your xon backend account")
|
|
418
|
+
.requiredOption("-r, --instance-url <url>", "Instance URL of the org")
|
|
419
|
+
.option("-a, --alias <alias>", "Alias to save this org connection under")
|
|
420
|
+
.option("-d, --set-default", "Set this org as the default", false)
|
|
421
|
+
.option("-b, --base-url <url>", "API base URL")
|
|
422
|
+
.option("-t, --token <token>", "Bearer token")
|
|
423
|
+
.action(async (options) => {
|
|
424
|
+
const password = await readStdin();
|
|
425
|
+
if (!password) {
|
|
426
|
+
throw new Error("Pipe the password via stdin, e.g. echo $PASSWORD | xon org login credentials -u <loginId> -r <url>.");
|
|
427
|
+
}
|
|
428
|
+
const client = await apiClient(options);
|
|
429
|
+
const record = await loginCredentials(client, {
|
|
430
|
+
loginId: options.loginId,
|
|
431
|
+
password,
|
|
432
|
+
instanceUrl: options.instanceUrl,
|
|
433
|
+
alias: options.alias,
|
|
434
|
+
setDefault: options.setDefault,
|
|
435
|
+
});
|
|
436
|
+
console.log(`Logged in to ${record.instanceUrl} as ${record.username} (alias: ${record.alias})`);
|
|
437
|
+
await saveProjectLoginDefaults(client.baseUrl, record.alias);
|
|
258
438
|
});
|
|
259
439
|
org
|
|
260
440
|
.command("logout")
|
|
@@ -264,7 +444,7 @@ org
|
|
|
264
444
|
.option("-b, --base-url <url>", "API base URL")
|
|
265
445
|
.option("-t, --token <token>", "Bearer token")
|
|
266
446
|
.action(async (options) => {
|
|
267
|
-
const client = apiClient(options);
|
|
447
|
+
const client = await apiClient(options);
|
|
268
448
|
if (options.all) {
|
|
269
449
|
const { orgs } = await listOrgs();
|
|
270
450
|
for (const record of orgs) {
|
|
@@ -274,7 +454,7 @@ org
|
|
|
274
454
|
await clearOrgs();
|
|
275
455
|
return;
|
|
276
456
|
}
|
|
277
|
-
const record = await logoutOrg(client, options.targetOrg);
|
|
457
|
+
const record = await logoutOrg(client, await resolveTargetOrg(options.targetOrg));
|
|
278
458
|
console.log(`Logged out of ${record.alias}`);
|
|
279
459
|
});
|
|
280
460
|
org
|
|
@@ -284,7 +464,7 @@ org
|
|
|
284
464
|
.option("-b, --base-url <url>", "API base URL")
|
|
285
465
|
.option("-t, --token <token>", "Bearer token")
|
|
286
466
|
.action(async (options) => {
|
|
287
|
-
output(await displayOrg(apiClient(options), options.targetOrg));
|
|
467
|
+
output(await displayOrg(await apiClient(options), await resolveTargetOrg(options.targetOrg)));
|
|
288
468
|
});
|
|
289
469
|
org
|
|
290
470
|
.command("open")
|
|
@@ -295,7 +475,7 @@ org
|
|
|
295
475
|
.option("-b, --base-url <url>", "API base URL")
|
|
296
476
|
.option("-t, --token <token>", "Bearer token")
|
|
297
477
|
.action(async (options) => {
|
|
298
|
-
const { url, org: record } = await openOrg(apiClient(options), { alias: options.targetOrg, path: options.path });
|
|
478
|
+
const { url, org: record } = await openOrg(await apiClient(options), { alias: await resolveTargetOrg(options.targetOrg), path: options.path });
|
|
299
479
|
if (options.urlOnly) {
|
|
300
480
|
console.log(url);
|
|
301
481
|
}
|
|
@@ -319,7 +499,7 @@ deploy
|
|
|
319
499
|
.option("-t, --token <token>", "Bearer token")
|
|
320
500
|
.action(async (options) => {
|
|
321
501
|
const payload = JSON.parse(await (await import("node:fs/promises")).readFile(options.file, "utf8"));
|
|
322
|
-
output(await apiClient(options).post("/deployRequests", payload));
|
|
502
|
+
output(await (await apiClient(options)).post("/deployRequests", payload));
|
|
323
503
|
});
|
|
324
504
|
deploy
|
|
325
505
|
.command("get <id>")
|
|
@@ -327,7 +507,7 @@ deploy
|
|
|
327
507
|
.option("-b, --base-url <url>", "API base URL")
|
|
328
508
|
.option("-t, --token <token>", "Bearer token")
|
|
329
509
|
.action(async (id, options) => {
|
|
330
|
-
output(await apiClient(options).get(`/deployRequests/${encodeURIComponent(id)}`));
|
|
510
|
+
output(await (await apiClient(options)).get(`/deployRequests/${encodeURIComponent(id)}`));
|
|
331
511
|
});
|
|
332
512
|
program.parseAsync().catch((error) => {
|
|
333
513
|
if (error instanceof CommanderError) {
|
package/dist/deploy.js
CHANGED
|
@@ -1,11 +1,45 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { pollJob } from "./polling.js";
|
|
2
3
|
import { collectSourceFiles } from "./source.js";
|
|
3
|
-
export
|
|
4
|
-
|
|
4
|
+
export const TEST_LEVELS = ["NoTestRun", "RunSpecifiedTests", "RunLocalTests", "RunAllTestsInOrg", "RunRelevantTests"];
|
|
5
|
+
export const COVERAGE_FORMATTERS = [
|
|
6
|
+
"clover",
|
|
7
|
+
"cobertura",
|
|
8
|
+
"html-spa",
|
|
9
|
+
"html",
|
|
10
|
+
"json",
|
|
11
|
+
"json-summary",
|
|
12
|
+
"lcovonly",
|
|
13
|
+
"none",
|
|
14
|
+
"teamcity",
|
|
15
|
+
"text",
|
|
16
|
+
"text-summary",
|
|
17
|
+
];
|
|
18
|
+
export async function startDeploy(client, sourceDirs, options) {
|
|
19
|
+
const source = (await Promise.all(sourceDirs.map((dir) => collectSourceFiles(dir)))).flat();
|
|
20
|
+
const metadataDir = options.metadataDir ? await collectSourceFiles(options.metadataDir) : undefined;
|
|
21
|
+
const manifest = options.manifest ? await readFile(options.manifest, "utf8") : undefined;
|
|
22
|
+
const preDestructiveChanges = options.preDestructiveChanges ? await readFile(options.preDestructiveChanges, "utf8") : undefined;
|
|
23
|
+
const postDestructiveChanges = options.postDestructiveChanges ? await readFile(options.postDestructiveChanges, "utf8") : undefined;
|
|
5
24
|
return client.post("/deployRequests", {
|
|
6
25
|
source,
|
|
7
26
|
targetOrg: options.targetOrg,
|
|
8
27
|
checkOnly: options.checkOnly ?? false,
|
|
28
|
+
apiVersion: options.apiVersion,
|
|
29
|
+
manifest,
|
|
30
|
+
metadata: options.metadata,
|
|
31
|
+
metadataDir,
|
|
32
|
+
singlePackage: options.singlePackage,
|
|
33
|
+
testLevel: options.testLevel,
|
|
34
|
+
tests: options.tests,
|
|
35
|
+
ignoreErrors: options.ignoreErrors,
|
|
36
|
+
ignoreWarnings: options.ignoreWarnings,
|
|
37
|
+
ignoreConflicts: options.ignoreConflicts,
|
|
38
|
+
purgeOnDelete: options.purgeOnDelete,
|
|
39
|
+
preDestructiveChanges,
|
|
40
|
+
postDestructiveChanges,
|
|
41
|
+
coverageFormatters: options.coverageFormatters,
|
|
42
|
+
junit: options.junit,
|
|
9
43
|
});
|
|
10
44
|
}
|
|
11
45
|
export async function reportDeploy(client, id) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function toCamelCase(fileName) {
|
|
4
|
+
const base = fileName.replace(/\.[^.]+$/, "");
|
|
5
|
+
return base.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
6
|
+
}
|
|
7
|
+
export async function applyFlagsDir(command, dir) {
|
|
8
|
+
let entries;
|
|
9
|
+
try {
|
|
10
|
+
entries = await readdir(dir);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
throw new Error(`--flags-dir directory not found: ${dir}`);
|
|
14
|
+
}
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const key = toCamelCase(entry);
|
|
17
|
+
if (command.getOptionValueSource(key) !== "default") {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const content = (await readFile(path.join(dir, entry), "utf8")).trim();
|
|
21
|
+
if (content.length === 0) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const current = command.getOptionValue(key);
|
|
25
|
+
if (typeof current === "boolean") {
|
|
26
|
+
command.setOptionValueWithSource(key, content !== "false", "cli");
|
|
27
|
+
}
|
|
28
|
+
else if (Array.isArray(current)) {
|
|
29
|
+
command.setOptionValueWithSource(key, content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean), "cli");
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
command.setOptionValueWithSource(key, content, "cli");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
package/dist/org.js
CHANGED
|
@@ -58,6 +58,18 @@ export async function loginAccessToken(client, options) {
|
|
|
58
58
|
});
|
|
59
59
|
return finishLogin(response, options.alias, options.setDefault);
|
|
60
60
|
}
|
|
61
|
+
export async function loginCredentials(client, options) {
|
|
62
|
+
const { token } = await client.post("/auth/login", {
|
|
63
|
+
loginId: options.loginId,
|
|
64
|
+
password: options.password,
|
|
65
|
+
});
|
|
66
|
+
return loginAccessToken(client, {
|
|
67
|
+
instanceUrl: options.instanceUrl,
|
|
68
|
+
accessToken: token,
|
|
69
|
+
alias: options.alias,
|
|
70
|
+
setDefault: options.setDefault,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
61
73
|
export async function logoutOrg(client, alias) {
|
|
62
74
|
const record = await getOrg(alias);
|
|
63
75
|
await client.post(`/orgs/${encodeURIComponent(record.alias)}/logout`, undefined);
|
package/dist/project.js
CHANGED
|
@@ -17,3 +17,16 @@ export async function readProject(directory = ".") {
|
|
|
17
17
|
const contents = await readFile(path.resolve(directory, projectFile), "utf8");
|
|
18
18
|
return JSON.parse(contents);
|
|
19
19
|
}
|
|
20
|
+
export async function updateProjectConfig(directory, patch) {
|
|
21
|
+
const config = await readProject(directory);
|
|
22
|
+
const updated = { ...config, ...patch };
|
|
23
|
+
await writeFile(path.resolve(directory, projectFile), `${JSON.stringify(updated, null, 2)}\n`);
|
|
24
|
+
}
|
|
25
|
+
export async function tryReadProject(directory = ".") {
|
|
26
|
+
try {
|
|
27
|
+
return await readProject(directory);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
package/dist/retrieve.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { pollJob } from "./polling.js";
|
|
2
|
-
export async function startRetrieve(client,
|
|
3
|
-
|
|
3
|
+
export async function startRetrieve(client, options) {
|
|
4
|
+
const manifest = options.manifest ? await readFile(options.manifest, "utf8") : undefined;
|
|
5
|
+
return client.post("/retrieveRequests", {
|
|
6
|
+
targetOrg: options.targetOrg,
|
|
7
|
+
apiVersion: options.apiVersion,
|
|
8
|
+
manifest,
|
|
9
|
+
metadata: options.metadata,
|
|
10
|
+
packageNames: options.packageNames,
|
|
11
|
+
sourceDir: options.sourceDir,
|
|
12
|
+
singlePackage: options.singlePackage,
|
|
13
|
+
ignoreConflicts: options.ignoreConflicts,
|
|
14
|
+
zipFileName: options.zipFileName,
|
|
15
|
+
});
|
|
4
16
|
}
|
|
5
17
|
export async function resumeRetrieve(client, id, waitSeconds) {
|
|
6
18
|
return pollJob(client, `/retrieveRequests/${encodeURIComponent(id)}`, { waitSeconds });
|