@cyberxon/xon 0.2.2 → 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 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 { displayOrg, loginAccessToken, loginCredentials, loginJwt, loginSfdxUrl, loginWeb, logoutOrg, openInBrowser, openOrg } from "./org.js";
12
13
  import { clearOrgs, listOrgs } 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,10 +24,11 @@ async function readStdin() {
22
24
  }
23
25
  return Buffer.concat(chunks).toString("utf8").trim();
24
26
  }
25
- function apiClient(options) {
26
- const baseUrl = options.baseUrl ?? process.env.XON_BASE_URL;
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 or pass --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
  return new ApiClient({ baseUrl, token: options.token ?? process.env.XON_TOKEN });
31
34
  }
@@ -37,6 +40,21 @@ async function requireProjectRoot() {
37
40
  throw new Error("Not in a xon project directory (missing .xon/project.json). Run this inside a project created with 'xon project create'.");
38
41
  }
39
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
+ }
40
58
  async function waitForDeploy(client, status, waitSeconds) {
41
59
  if (waitSeconds > 0 && !status.done) {
42
60
  return resumeDeploy(client, status.id, waitSeconds);
@@ -50,6 +68,68 @@ const parseWait = (value) => {
50
68
  }
51
69
  return parsed;
52
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
+ }
53
133
  program
54
134
  .name("xon")
55
135
  .description("Manage Xon projects and deployments")
@@ -65,40 +145,72 @@ project
65
145
  console.log(`Created project ${name} in ${root}`);
66
146
  });
67
147
  const projectDeploy = project.command("deploy").description("Deploy metadata between your project and an org");
68
- projectDeploy
69
- .command("start")
70
- .description("Deploy metadata to an org from your local project")
71
- .option("-d, --source-dir <dir>", "Directory of source to deploy", "src")
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) => {
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) => {
78
152
  await requireProjectRoot();
79
- const client = apiClient(options);
80
- const status = await startDeploy(client, path.resolve(options.sourceDir), {
81
- targetOrg: options.targetOrg,
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),
82
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,
83
177
  });
84
- output(await waitForDeploy(client, status, options.wait));
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);
85
182
  });
86
- projectDeploy
87
- .command("validate")
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) => {
183
+ addDeployStartFlags(projectDeploy.command("validate").description("Validate a metadata deployment without actually executing it"))
184
+ .action(async (options, command) => {
95
185
  await requireProjectRoot();
96
- const client = apiClient(options);
97
- const status = await startDeploy(client, path.resolve(options.sourceDir), {
98
- targetOrg: options.targetOrg,
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),
99
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,
100
210
  });
101
- output(await waitForDeploy(client, status, options.wait));
211
+ const final = await waitForDeploy(client, status, options.wait);
212
+ await writeDeployResults(options.resultsDir, final);
213
+ printDeployResult(final, options);
102
214
  });
103
215
  projectDeploy
104
216
  .command("quick")
@@ -108,7 +220,7 @@ projectDeploy
108
220
  .option("-b, --base-url <url>", "API base URL")
109
221
  .option("-t, --token <token>", "Bearer token")
110
222
  .action(async (options) => {
111
- const client = apiClient(options);
223
+ const client = await apiClient(options);
112
224
  const status = await quickDeploy(client, options.jobId);
113
225
  output(await waitForDeploy(client, status, options.wait));
114
226
  });
@@ -119,7 +231,7 @@ projectDeploy
119
231
  .option("-b, --base-url <url>", "API base URL")
120
232
  .option("-t, --token <token>", "Bearer token")
121
233
  .action(async (options) => {
122
- output(await reportDeploy(apiClient(options), options.jobId));
234
+ output(await reportDeploy(await apiClient(options), options.jobId));
123
235
  });
124
236
  projectDeploy
125
237
  .command("resume")
@@ -129,7 +241,7 @@ projectDeploy
129
241
  .option("-b, --base-url <url>", "API base URL")
130
242
  .option("-t, --token <token>", "Bearer token")
131
243
  .action(async (options) => {
132
- output(await resumeDeploy(apiClient(options), options.jobId, options.wait));
244
+ output(await resumeDeploy(await apiClient(options), options.jobId, options.wait));
133
245
  });
134
246
  projectDeploy
135
247
  .command("cancel")
@@ -138,7 +250,7 @@ projectDeploy
138
250
  .option("-b, --base-url <url>", "API base URL")
139
251
  .option("-t, --token <token>", "Bearer token")
140
252
  .action(async (options) => {
141
- output(await cancelDeploy(apiClient(options), options.jobId));
253
+ output(await cancelDeploy(await apiClient(options), options.jobId));
142
254
  });
143
255
  projectDeploy
144
256
  .command("preview")
@@ -153,23 +265,56 @@ const projectRetrieve = project.command("retrieve").description("Retrieve metada
153
265
  projectRetrieve
154
266
  .command("start")
155
267
  .description("Retrieve metadata from an org to your local project")
156
- .option("-d, --source-dir <dir>", "Directory to write retrieved source into", "src")
157
- .option("-o, --target-org <org>", "Org to retrieve from")
158
- .option("-w, --wait <seconds>", "Seconds to wait for the retrieve to finish", parseWait, 60)
159
- .option("-b, --base-url <url>", "API base URL")
160
- .option("-t, --token <token>", "Bearer token")
161
- .action(async (options) => {
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) => {
162
286
  await requireProjectRoot();
163
- const client = apiClient(options);
164
- let result = await startRetrieve(client, options.targetOrg);
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
+ });
165
303
  if (options.wait > 0 && !result.done) {
166
304
  result = await resumeRetrieve(client, result.id, options.wait);
167
305
  }
168
- if (result.success && result.files) {
169
- await writeSourceFiles(path.resolve(options.sourceDir), result.files);
170
- console.log(`Retrieved ${result.files.length} file(s) into ${options.sourceDir}`);
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}`);
171
309
  }
172
- const { files, ...summary } = result;
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;
173
318
  output(summary);
174
319
  });
175
320
  projectRetrieve
@@ -179,7 +324,7 @@ projectRetrieve
179
324
  .option("-b, --base-url <url>", "API base URL")
180
325
  .option("-t, --token <token>", "Bearer token")
181
326
  .action(async (options) => {
182
- const rows = await previewRetrieve(apiClient(options), options.targetOrg);
327
+ const rows = await previewRetrieve(await apiClient(options), options.targetOrg);
183
328
  console.table(rows);
184
329
  });
185
330
  const org = program.command("org").description("Manage your Salesforce org connections");
@@ -194,13 +339,15 @@ orgLogin
194
339
  .option("-b, --base-url <url>", "API base URL")
195
340
  .option("-t, --token <token>", "Bearer token")
196
341
  .action(async (options) => {
197
- const record = await loginWeb(apiClient(options), {
342
+ const client = await apiClient(options);
343
+ const record = await loginWeb(client, {
198
344
  alias: options.alias,
199
345
  setDefault: options.setDefault,
200
346
  waitSeconds: options.wait,
201
347
  instanceUrl: options.instanceUrl,
202
348
  });
203
349
  console.log(`Logged in to ${record.instanceUrl} as ${record.username} (alias: ${record.alias})`);
350
+ await saveProjectLoginDefaults(client.baseUrl, record.alias);
204
351
  });
205
352
  orgLogin
206
353
  .command("jwt")
@@ -214,8 +361,10 @@ orgLogin
214
361
  .option("-b, --base-url <url>", "API base URL")
215
362
  .option("-t, --token <token>", "Bearer token")
216
363
  .action(async (options) => {
217
- const record = await loginJwt(apiClient(options), options);
364
+ const client = await apiClient(options);
365
+ const record = await loginJwt(client, options);
218
366
  console.log(`Logged in to ${record.instanceUrl} as ${record.username} (alias: ${record.alias})`);
367
+ await saveProjectLoginDefaults(client.baseUrl, record.alias);
219
368
  });
220
369
  orgLogin
221
370
  .command("sfdx-url")
@@ -234,8 +383,10 @@ orgLogin
234
383
  : (() => {
235
384
  throw new Error("Pass --sfdx-url-file <path> or --sfdx-url-stdin.");
236
385
  })();
237
- const record = await loginSfdxUrl(apiClient(options), { sfdxAuthUrl, alias: options.alias, setDefault: options.setDefault });
386
+ const client = await apiClient(options);
387
+ const record = await loginSfdxUrl(client, { sfdxAuthUrl, alias: options.alias, setDefault: options.setDefault });
238
388
  console.log(`Logged in to ${record.instanceUrl} as ${record.username} (alias: ${record.alias})`);
389
+ await saveProjectLoginDefaults(client.baseUrl, record.alias);
239
390
  });
240
391
  orgLogin
241
392
  .command("access-token")
@@ -250,13 +401,40 @@ orgLogin
250
401
  if (!accessToken) {
251
402
  throw new Error("Pipe the access token via stdin, e.g. echo $TOKEN | xon org login access-token -r <url>.");
252
403
  }
253
- const record = await loginAccessToken(apiClient(options), {
404
+ const client = await apiClient(options);
405
+ const record = await loginAccessToken(client, {
254
406
  instanceUrl: options.instanceUrl,
255
407
  accessToken,
256
408
  alias: options.alias,
257
409
  setDefault: options.setDefault,
258
410
  });
259
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);
260
438
  });
261
439
  org
262
440
  .command("logout")
@@ -266,7 +444,7 @@ org
266
444
  .option("-b, --base-url <url>", "API base URL")
267
445
  .option("-t, --token <token>", "Bearer token")
268
446
  .action(async (options) => {
269
- const client = apiClient(options);
447
+ const client = await apiClient(options);
270
448
  if (options.all) {
271
449
  const { orgs } = await listOrgs();
272
450
  for (const record of orgs) {
@@ -276,7 +454,7 @@ org
276
454
  await clearOrgs();
277
455
  return;
278
456
  }
279
- const record = await logoutOrg(client, options.targetOrg);
457
+ const record = await logoutOrg(client, await resolveTargetOrg(options.targetOrg));
280
458
  console.log(`Logged out of ${record.alias}`);
281
459
  });
282
460
  org
@@ -286,7 +464,7 @@ org
286
464
  .option("-b, --base-url <url>", "API base URL")
287
465
  .option("-t, --token <token>", "Bearer token")
288
466
  .action(async (options) => {
289
- output(await displayOrg(apiClient(options), options.targetOrg));
467
+ output(await displayOrg(await apiClient(options), await resolveTargetOrg(options.targetOrg)));
290
468
  });
291
469
  org
292
470
  .command("open")
@@ -297,7 +475,7 @@ org
297
475
  .option("-b, --base-url <url>", "API base URL")
298
476
  .option("-t, --token <token>", "Bearer token")
299
477
  .action(async (options) => {
300
- 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 });
301
479
  if (options.urlOnly) {
302
480
  console.log(url);
303
481
  }
@@ -321,7 +499,7 @@ deploy
321
499
  .option("-t, --token <token>", "Bearer token")
322
500
  .action(async (options) => {
323
501
  const payload = JSON.parse(await (await import("node:fs/promises")).readFile(options.file, "utf8"));
324
- output(await apiClient(options).post("/deployRequests", payload));
502
+ output(await (await apiClient(options)).post("/deployRequests", payload));
325
503
  });
326
504
  deploy
327
505
  .command("get <id>")
@@ -329,7 +507,7 @@ deploy
329
507
  .option("-b, --base-url <url>", "API base URL")
330
508
  .option("-t, --token <token>", "Bearer token")
331
509
  .action(async (id, options) => {
332
- output(await apiClient(options).get(`/deployRequests/${encodeURIComponent(id)}`));
510
+ output(await (await apiClient(options)).get(`/deployRequests/${encodeURIComponent(id)}`));
333
511
  });
334
512
  program.parseAsync().catch((error) => {
335
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 async function startDeploy(client, sourceDir, options) {
4
- const source = await collectSourceFiles(sourceDir);
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, targetOrg) {
3
- return client.post("/retrieveRequests", { targetOrg });
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 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyberxon/xon",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "A CLI for Xon projects and deployments",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",