@salesforce-sentry/cli-shared 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@salesforce-sentry/cli-shared",
3
+ "version": "0.1.0",
4
+ "description": "Shared utilities for salesforce-sentry CLI tools",
5
+ "license": "MIT",
6
+ "files": [
7
+ "utils/"
8
+ ],
9
+ "dependencies": {
10
+ "diff": "^5.2.0",
11
+ "fast-xml-parser": "^4.3.6",
12
+ "picocolors": "^1.1.0",
13
+ "prompts": "^2.4.2"
14
+ }
15
+ }
package/utils/dsn.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ // https://<publicKey>@<host>/<projectId>
4
+ function parseDSN(dsn) {
5
+ if (!dsn || typeof dsn !== "string") return { valid: false };
6
+ const match = dsn.match(/^https?:\/\/([^@]+)@([^/]+)\/(\d+)$/);
7
+ if (!match) return { valid: false };
8
+ const [, publicKey, host, projectId] = match;
9
+ return {
10
+ valid: true,
11
+ publicKey,
12
+ host,
13
+ projectId,
14
+ remoteUrl: `https://${host}`
15
+ };
16
+ }
17
+
18
+ function validateDSN(dsn) {
19
+ const parsed = parseDSN(dsn);
20
+ if (!parsed.valid)
21
+ return "Invalid DSN — expected https://<key>@<host>/<projectId>";
22
+ return null;
23
+ }
24
+
25
+ module.exports = { parseDSN, validateDSN };
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+
3
+ const { createPatch } = require("diff");
4
+ const pc = require("picocolors");
5
+ const prompts = require("prompts");
6
+
7
+ function colorDiff(patch) {
8
+ return patch
9
+ .split("\n")
10
+ .map((line) => {
11
+ if (line.startsWith("+++") || line.startsWith("---"))
12
+ return pc.bold(line);
13
+ if (line.startsWith("+")) return pc.green(line);
14
+ if (line.startsWith("-")) return pc.red(line);
15
+ if (line.startsWith("@@")) return pc.cyan(line);
16
+ return pc.dim(line);
17
+ })
18
+ .join("\n");
19
+ }
20
+
21
+ async function showDiffAndPrompt(transform) {
22
+ const relPath = transform.path.replace(process.cwd() + "/", "");
23
+ const divider = pc.dim("─".repeat(60));
24
+
25
+ console.log("\n" + divider);
26
+ console.log(pc.bold(" " + transform.label));
27
+ console.log(pc.dim(" " + relPath));
28
+ console.log(divider);
29
+
30
+ const patch = createPatch(
31
+ relPath,
32
+ transform.oldContent,
33
+ transform.newContent,
34
+ "",
35
+ "",
36
+ { context: 3 }
37
+ );
38
+ const hunks = patch.split("\n").slice(4).join("\n");
39
+ console.log(colorDiff(hunks));
40
+
41
+ const { decision } = await prompts(
42
+ {
43
+ type: "select",
44
+ name: "decision",
45
+ message: "Apply this change?",
46
+ choices: [
47
+ { title: pc.green("Yes"), value: "yes" },
48
+ { title: pc.dim("No"), value: "no" },
49
+ { title: pc.yellow("All remaining"), value: "all" },
50
+ { title: pc.red("Quit"), value: "quit" }
51
+ ],
52
+ initial: 0
53
+ },
54
+ { onCancel: () => ({ decision: "quit" }) }
55
+ );
56
+
57
+ return decision ?? "quit";
58
+ }
59
+
60
+ module.exports = { colorDiff, showDiffAndPrompt };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const { XMLParser } = require("fast-xml-parser");
5
+
6
+ const parser = new XMLParser({ ignoreAttributes: false, parseTagValue: true });
7
+
8
+ function readMetaXml(filePath) {
9
+ try {
10
+ return parser.parse(fs.readFileSync(filePath, "utf8"));
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ module.exports = { readMetaXml };
package/utils/sfdx.js ADDED
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const SKIP_DIRS = new Set([
7
+ "node_modules",
8
+ "__tests__",
9
+ ".git",
10
+ ".sfdx",
11
+ ".sf"
12
+ ]);
13
+
14
+ function readSfdxProject(projectRoot) {
15
+ const sfdxPath = path.join(projectRoot, "sfdx-project.json");
16
+ if (!fs.existsSync(sfdxPath)) {
17
+ throw new Error(
18
+ `No sfdx-project.json found in ${projectRoot}.\n` +
19
+ "Run this tool from the root of an SFDX project."
20
+ );
21
+ }
22
+ try {
23
+ return JSON.parse(fs.readFileSync(sfdxPath, "utf8"));
24
+ } catch (e) {
25
+ throw new Error(`Could not parse sfdx-project.json: ${e.message}`);
26
+ }
27
+ }
28
+
29
+ function getSourceDirs(projectRoot) {
30
+ const project = readSfdxProject(projectRoot);
31
+ const packageDirs = project.packageDirectories ?? [];
32
+ if (packageDirs.length === 0) {
33
+ throw new Error("sfdx-project.json has no packageDirectories entries.");
34
+ }
35
+ return packageDirs
36
+ .map((pkg) => path.resolve(projectRoot, pkg.path))
37
+ .filter((dir) => fs.existsSync(dir));
38
+ }
39
+
40
+ function getDefaultSourceDir(projectRoot) {
41
+ const project = readSfdxProject(projectRoot);
42
+ const entry =
43
+ project.packageDirectories?.find((d) => d.default) ??
44
+ project.packageDirectories?.[0];
45
+ if (!entry) {
46
+ throw new Error("No packageDirectories found in sfdx-project.json");
47
+ }
48
+ return path.resolve(projectRoot, entry.path);
49
+ }
50
+
51
+ function findFiles(dir, predicate, results = []) {
52
+ let entries;
53
+ try {
54
+ entries = fs.readdirSync(dir, { withFileTypes: true });
55
+ } catch {
56
+ return results;
57
+ }
58
+ for (const entry of entries) {
59
+ if (entry.isDirectory()) {
60
+ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
61
+ findFiles(path.join(dir, entry.name), predicate, results);
62
+ }
63
+ } else if (predicate(path.join(dir, entry.name))) {
64
+ results.push(path.join(dir, entry.name));
65
+ }
66
+ }
67
+ return results;
68
+ }
69
+
70
+ function findProjectFiles(projectRoot, predicate) {
71
+ const results = [];
72
+ for (const dir of getSourceDirs(projectRoot)) {
73
+ findFiles(dir, predicate, results);
74
+ }
75
+ return results;
76
+ }
77
+
78
+ module.exports = {
79
+ readSfdxProject,
80
+ getSourceDirs,
81
+ getDefaultSourceDir,
82
+ findProjectFiles
83
+ };