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