sf-plugin-permission-sets 0.0.0-dev.9 → 0.2.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/README.md +126 -79
- package/lib/adapters/connection-org-client.d.ts +16 -0
- package/lib/adapters/connection-org-client.js +248 -0
- package/lib/adapters/connection-org-client.js.map +1 -0
- package/lib/commands/ps/apply.d.ts +27 -0
- package/lib/commands/ps/apply.js +125 -0
- package/lib/commands/ps/apply.js.map +1 -0
- package/lib/commands/ps/check.d.ts +18 -0
- package/lib/commands/ps/check.js +46 -0
- package/lib/commands/ps/check.js.map +1 -0
- package/lib/commands/ps/export.d.ts +17 -0
- package/lib/commands/ps/export.js +29 -0
- package/lib/commands/ps/export.js.map +1 -0
- package/lib/commands/ps/plan.d.ts +22 -0
- package/lib/commands/ps/plan.js +78 -0
- package/lib/commands/ps/plan.js.map +1 -0
- package/lib/commands/ps/validate.d.ts +19 -0
- package/lib/commands/ps/validate.js +48 -0
- package/lib/commands/ps/validate.js.map +1 -0
- package/lib/core/diff.d.ts +9 -0
- package/lib/core/diff.js +53 -0
- package/lib/core/diff.js.map +1 -0
- package/lib/core/finding.d.ts +39 -0
- package/lib/core/finding.js +68 -0
- package/lib/core/finding.js.map +1 -0
- package/lib/core/load.d.ts +3 -0
- package/lib/core/load.js +53 -0
- package/lib/core/load.js.map +1 -0
- package/lib/core/model.d.ts +68 -0
- package/lib/core/model.js +2 -0
- package/lib/core/model.js.map +1 -0
- package/lib/core/normalize.d.ts +14 -0
- package/lib/core/normalize.js +44 -0
- package/lib/core/normalize.js.map +1 -0
- package/lib/core/parse.d.ts +9 -0
- package/lib/core/parse.js +20 -0
- package/lib/core/parse.js.map +1 -0
- package/lib/core/report.d.ts +7 -0
- package/lib/core/report.js +62 -0
- package/lib/core/report.js.map +1 -0
- package/lib/core/resolve.d.ts +19 -0
- package/lib/core/resolve.js +88 -0
- package/lib/core/resolve.js.map +1 -0
- package/lib/core/schema.d.ts +32 -0
- package/lib/core/schema.js +30 -0
- package/lib/core/schema.js.map +1 -0
- package/lib/core/serialize.d.ts +8 -0
- package/lib/core/serialize.js +34 -0
- package/lib/core/serialize.js.map +1 -0
- package/lib/services/adapters/org-client.d.ts +21 -0
- package/lib/services/adapters/org-client.js +2 -0
- package/lib/services/adapters/org-client.js.map +1 -0
- package/lib/services/apply.d.ts +41 -0
- package/lib/services/apply.js +118 -0
- package/lib/services/apply.js.map +1 -0
- package/lib/services/check.d.ts +17 -0
- package/lib/services/check.js +25 -0
- package/lib/services/check.js.map +1 -0
- package/lib/services/export.d.ts +13 -0
- package/lib/services/export.js +25 -0
- package/lib/services/export.js.map +1 -0
- package/lib/services/plan.d.ts +36 -0
- package/lib/services/plan.js +71 -0
- package/lib/services/plan.js.map +1 -0
- package/lib/services/validate.d.ts +20 -0
- package/lib/services/validate.js +43 -0
- package/lib/services/validate.js.map +1 -0
- package/messages/ps.apply.md +81 -0
- package/messages/ps.check.md +33 -0
- package/messages/ps.export.md +25 -0
- package/messages/ps.plan.md +45 -0
- package/messages/ps.validate.md +29 -0
- package/oclif.lock +570 -1
- package/oclif.manifest.json +364 -9
- package/package.json +22 -31
- package/lib/commands/ps/info.d.ts +0 -11
- package/lib/commands/ps/info.js +0 -17
- package/lib/commands/ps/info.js.map +0 -1
- package/messages/ps.info.md +0 -17
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { loadFiles } from '../core/load.js';
|
|
2
|
+
import { diffAssignments } from '../core/diff.js';
|
|
3
|
+
import { kinds, distinctAssignees, distinctTargets, evaluateUsers, evaluateTargets, indexUsersById, indexTargetsById, } from '../core/resolve.js';
|
|
4
|
+
import { countFindings } from '../core/finding.js';
|
|
5
|
+
const emptyDiff = { toAdd: [], toUpdate: [], toRemove: [], unchanged: [] };
|
|
6
|
+
/** An aborted-before-any-change result, carrying the findings that explain why. */
|
|
7
|
+
function invalidResult(files, findings) {
|
|
8
|
+
return {
|
|
9
|
+
files,
|
|
10
|
+
findings,
|
|
11
|
+
diff: emptyDiff,
|
|
12
|
+
drift: { adds: 0, updates: 0, removes: 0 },
|
|
13
|
+
outcomes: [],
|
|
14
|
+
status: 'invalid',
|
|
15
|
+
failed: true,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** The resolved ids of every declared target, to fetch their current memberships. */
|
|
19
|
+
function managedTargets(resolution) {
|
|
20
|
+
const refs = [];
|
|
21
|
+
for (const kind of kinds) {
|
|
22
|
+
for (const id of resolution.targetIds[kind].values()) {
|
|
23
|
+
refs.push({ kind, id });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return refs;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Online apply: load the files, resolve every reference to an org id, diff against
|
|
30
|
+
* the org's current state, then add and/or remove per the mode. Deletions are
|
|
31
|
+
* capped by maxDeletes and gated by an injected confirmation.
|
|
32
|
+
*/
|
|
33
|
+
export class ApplyService {
|
|
34
|
+
org;
|
|
35
|
+
files;
|
|
36
|
+
input;
|
|
37
|
+
confirmDeletions;
|
|
38
|
+
constructor(org, files, input, confirmDeletions) {
|
|
39
|
+
this.org = org;
|
|
40
|
+
this.files = files;
|
|
41
|
+
this.input = input;
|
|
42
|
+
this.confirmDeletions = confirmDeletions;
|
|
43
|
+
}
|
|
44
|
+
async run() {
|
|
45
|
+
const loaded = await loadFiles(this.files);
|
|
46
|
+
if (countFindings(loaded.findings).errors > 0) {
|
|
47
|
+
return invalidResult(loaded.files, loaded.findings);
|
|
48
|
+
}
|
|
49
|
+
const resolution = await this.resolve(loaded.assignments);
|
|
50
|
+
const findings = [...loaded.findings, ...resolution.findings];
|
|
51
|
+
if (countFindings(findings).errors > 0) {
|
|
52
|
+
return invalidResult(loaded.files, findings);
|
|
53
|
+
}
|
|
54
|
+
const actual = await this.org.currentAssignments(managedTargets(resolution));
|
|
55
|
+
const diff = diffAssignments(loaded.assignments, actual);
|
|
56
|
+
const { mode, maxDeletes, dryRun } = this.input;
|
|
57
|
+
const additions = mode === 'destructive' ? [] : diff.toAdd;
|
|
58
|
+
const updates = mode === 'destructive' ? [] : diff.toUpdate;
|
|
59
|
+
const removals = mode === 'additive' ? [] : diff.toRemove;
|
|
60
|
+
const drift = {
|
|
61
|
+
adds: mode === 'destructive' ? diff.toAdd.length : 0,
|
|
62
|
+
updates: mode === 'destructive' ? diff.toUpdate.length : 0,
|
|
63
|
+
removes: mode === 'additive' ? diff.toRemove.length : 0,
|
|
64
|
+
};
|
|
65
|
+
if (removals.length > maxDeletes) {
|
|
66
|
+
return {
|
|
67
|
+
files: loaded.files,
|
|
68
|
+
findings,
|
|
69
|
+
diff,
|
|
70
|
+
drift,
|
|
71
|
+
outcomes: [],
|
|
72
|
+
status: 'max-deletes-exceeded',
|
|
73
|
+
failed: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
return { files: loaded.files, findings, diff, drift, outcomes: [], status: 'dry-run', failed: false };
|
|
78
|
+
}
|
|
79
|
+
if (removals.length > 0) {
|
|
80
|
+
const confirmed = await this.confirmDeletions(removals.length);
|
|
81
|
+
if (!confirmed) {
|
|
82
|
+
return { files: loaded.files, findings, diff, drift, outcomes: [], status: 'declined', failed: false };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const outcomes = await this.execute(additions, updates, removals, resolution);
|
|
86
|
+
const failed = outcomes.some((outcome) => !outcome.success);
|
|
87
|
+
return { files: loaded.files, findings, diff, drift, outcomes, status: 'applied', failed };
|
|
88
|
+
}
|
|
89
|
+
/** Look every declared reference up in the org, returning findings and the id maps. */
|
|
90
|
+
async resolve(assignments) {
|
|
91
|
+
const usernames = distinctAssignees(assignments);
|
|
92
|
+
const targetsByKind = kinds.map((kind) => ({ kind, targets: distinctTargets(assignments, kind) }));
|
|
93
|
+
const usersTask = usernames.length > 0 ? this.org.findUsers(usernames) : Promise.resolve([]);
|
|
94
|
+
const targetsTask = Promise.all(targetsByKind.map(({ kind, targets }) => (targets.length > 0 ? this.org.findTargets(kind, targets) : Promise.resolve([])).then((found) => ({ kind, targets, found }))));
|
|
95
|
+
const [foundUsers, perKind] = await Promise.all([usersTask, targetsTask]);
|
|
96
|
+
const findings = [...evaluateUsers(usernames, foundUsers)];
|
|
97
|
+
const targetIds = {};
|
|
98
|
+
for (const { kind, targets, found } of perKind) {
|
|
99
|
+
findings.push(...evaluateTargets(kind, targets, found.map((target) => target.name)));
|
|
100
|
+
targetIds[kind] = indexTargetsById(found);
|
|
101
|
+
}
|
|
102
|
+
return { findings, userIds: indexUsersById(foundUsers), targetIds };
|
|
103
|
+
}
|
|
104
|
+
async execute(additions, updates, removals, resolution) {
|
|
105
|
+
const resolved = additions.map((addition) => ({
|
|
106
|
+
...addition,
|
|
107
|
+
assigneeId: resolution.userIds.get(addition.assignee.toLowerCase()) ?? '',
|
|
108
|
+
targetId: resolution.targetIds[addition.kind].get(addition.target.toLowerCase()) ?? '',
|
|
109
|
+
}));
|
|
110
|
+
const [added, updated, removed] = await Promise.all([
|
|
111
|
+
resolved.length > 0 ? this.org.addAssignments(resolved) : Promise.resolve([]),
|
|
112
|
+
updates.length > 0 ? this.org.updateAssignments(updates) : Promise.resolve([]),
|
|
113
|
+
removals.length > 0 ? this.org.removeAssignments(removals) : Promise.resolve([]),
|
|
114
|
+
]);
|
|
115
|
+
return [...added, ...updated, ...removed];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/services/apply.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DesiredAssignment } from '../core/model.js';
|
|
2
|
+
import { Finding } from '../core/finding.js';
|
|
3
|
+
export type CheckResult = {
|
|
4
|
+
files: string[];
|
|
5
|
+
assignments: DesiredAssignment[];
|
|
6
|
+
findings: Finding[];
|
|
7
|
+
errors: number;
|
|
8
|
+
warnings: number;
|
|
9
|
+
failed: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** Offline check: load the files, validate them, and summarize the findings. */
|
|
12
|
+
export declare class CheckService {
|
|
13
|
+
private readonly files;
|
|
14
|
+
private readonly strict;
|
|
15
|
+
constructor(files: string[], strict: boolean);
|
|
16
|
+
run(): Promise<CheckResult>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { loadFiles } from '../core/load.js';
|
|
2
|
+
import { countFindings } from '../core/finding.js';
|
|
3
|
+
/** Offline check: load the files, validate them, and summarize the findings. */
|
|
4
|
+
export class CheckService {
|
|
5
|
+
files;
|
|
6
|
+
strict;
|
|
7
|
+
constructor(files, strict) {
|
|
8
|
+
this.files = files;
|
|
9
|
+
this.strict = strict;
|
|
10
|
+
}
|
|
11
|
+
async run() {
|
|
12
|
+
const loaded = await loadFiles(this.files);
|
|
13
|
+
const { errors, warnings } = countFindings(loaded.findings);
|
|
14
|
+
const failed = errors > 0 || (this.strict && warnings > 0);
|
|
15
|
+
return {
|
|
16
|
+
files: loaded.files,
|
|
17
|
+
assignments: loaded.assignments,
|
|
18
|
+
findings: loaded.findings,
|
|
19
|
+
errors,
|
|
20
|
+
warnings,
|
|
21
|
+
failed,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/services/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAW,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAW5D,gFAAgF;AAChF,MAAM,OAAO,YAAY;IACe;IAAkC;IAAtE,YAAoC,KAAe,EAAmB,MAAe;QAAjD,UAAK,GAAL,KAAK,CAAU;QAAmB,WAAM,GAAN,MAAM,CAAS;IAAG,CAAC;IAElF,KAAK,CAAC,GAAG;QACZ,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;QAE3D,OAAO;YACH,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM;YACN,QAAQ;YACR,MAAM;SACT,CAAC;IACN,CAAC;CACJ"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { OrgClient } from './adapters/org-client.js';
|
|
2
|
+
export type ExportResult = {
|
|
3
|
+
outputFile: string;
|
|
4
|
+
users: number;
|
|
5
|
+
assignments: number;
|
|
6
|
+
};
|
|
7
|
+
/** Online export: read the org's current assignments and write them as a YAML file. */
|
|
8
|
+
export declare class ExportService {
|
|
9
|
+
private readonly org;
|
|
10
|
+
private readonly outputFile;
|
|
11
|
+
constructor(org: OrgClient, outputFile: string);
|
|
12
|
+
run(): Promise<ExportResult>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { serializeAssignments } from '../core/serialize.js';
|
|
4
|
+
/** Online export: read the org's current assignments and write them as a YAML file. */
|
|
5
|
+
export class ExportService {
|
|
6
|
+
org;
|
|
7
|
+
outputFile;
|
|
8
|
+
constructor(org, outputFile) {
|
|
9
|
+
this.org = org;
|
|
10
|
+
this.outputFile = outputFile;
|
|
11
|
+
}
|
|
12
|
+
async run() {
|
|
13
|
+
const assignments = await this.org.listAssignments();
|
|
14
|
+
const content = serializeAssignments(assignments);
|
|
15
|
+
await mkdir(dirname(this.outputFile), { recursive: true });
|
|
16
|
+
await writeFile(this.outputFile, content, 'utf8');
|
|
17
|
+
const assignees = new Set(assignments.map((assignment) => assignment.assignee));
|
|
18
|
+
return {
|
|
19
|
+
outputFile: this.outputFile,
|
|
20
|
+
users: assignees.size,
|
|
21
|
+
assignments: assignments.length,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/services/export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAS5D,uFAAuF;AACvF,MAAM,OAAO,aAAa;IACc;IAAiC;IAArE,YAAoC,GAAc,EAAmB,UAAkB;QAAnD,QAAG,GAAH,GAAG,CAAW;QAAmB,eAAU,GAAV,UAAU,CAAQ;IAAG,CAAC;IAEpF,KAAK,CAAC,GAAG;QACZ,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QACrD,MAAM,OAAO,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;QAElD,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,MAAM,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAElD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;QAEhF,OAAO;YACH,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,KAAK,EAAE,SAAS,CAAC,IAAI;YACrB,WAAW,EAAE,WAAW,CAAC,MAAM;SAClC,CAAC;IACN,CAAC;CACJ"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Diff } from '../core/model.js';
|
|
2
|
+
import { Finding } from '../core/finding.js';
|
|
3
|
+
import { OrgClient } from './adapters/org-client.js';
|
|
4
|
+
export type PlanMode = 'additive' | 'destructive' | 'sync';
|
|
5
|
+
export type PlanInput = {
|
|
6
|
+
mode: PlanMode;
|
|
7
|
+
};
|
|
8
|
+
/** How a run ended, so the command can report and set the exit code. */
|
|
9
|
+
export type PlanStatus = 'planned' | 'invalid';
|
|
10
|
+
export type PlanResult = {
|
|
11
|
+
files: string[];
|
|
12
|
+
findings: Finding[];
|
|
13
|
+
diff: Diff;
|
|
14
|
+
/** What the chosen mode would not act on (surfaced as drift). */
|
|
15
|
+
drift: {
|
|
16
|
+
adds: number;
|
|
17
|
+
updates: number;
|
|
18
|
+
removes: number;
|
|
19
|
+
};
|
|
20
|
+
status: PlanStatus;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Read-only preview: load the files, resolve every reference to an org id, fetch the
|
|
24
|
+
* current state, and diff. The full diff (adds and would-be removes) is always returned
|
|
25
|
+
* regardless of mode; drift is whatever the chosen mode would not act on. Never changes
|
|
26
|
+
* the org. This is the apply pipeline stopping before any DML.
|
|
27
|
+
*/
|
|
28
|
+
export declare class PlanService {
|
|
29
|
+
private readonly org;
|
|
30
|
+
private readonly files;
|
|
31
|
+
private readonly input;
|
|
32
|
+
constructor(org: OrgClient, files: string[], input: PlanInput);
|
|
33
|
+
run(): Promise<PlanResult>;
|
|
34
|
+
/** Look every declared reference up in the org, returning findings and the target id maps. */
|
|
35
|
+
private resolve;
|
|
36
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { loadFiles } from '../core/load.js';
|
|
2
|
+
import { diffAssignments } from '../core/diff.js';
|
|
3
|
+
import { kinds, distinctAssignees, distinctTargets, evaluateUsers, evaluateTargets, indexTargetsById, } from '../core/resolve.js';
|
|
4
|
+
import { countFindings } from '../core/finding.js';
|
|
5
|
+
const emptyDiff = { toAdd: [], toUpdate: [], toRemove: [], unchanged: [] };
|
|
6
|
+
/** An aborted-before-the-diff result, carrying the findings that explain why. */
|
|
7
|
+
function invalidResult(files, findings) {
|
|
8
|
+
return { files, findings, diff: emptyDiff, drift: { adds: 0, updates: 0, removes: 0 }, status: 'invalid' };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Read-only preview: load the files, resolve every reference to an org id, fetch the
|
|
12
|
+
* current state, and diff. The full diff (adds and would-be removes) is always returned
|
|
13
|
+
* regardless of mode; drift is whatever the chosen mode would not act on. Never changes
|
|
14
|
+
* the org. This is the apply pipeline stopping before any DML.
|
|
15
|
+
*/
|
|
16
|
+
export class PlanService {
|
|
17
|
+
org;
|
|
18
|
+
files;
|
|
19
|
+
input;
|
|
20
|
+
constructor(org, files, input) {
|
|
21
|
+
this.org = org;
|
|
22
|
+
this.files = files;
|
|
23
|
+
this.input = input;
|
|
24
|
+
}
|
|
25
|
+
async run() {
|
|
26
|
+
const loaded = await loadFiles(this.files);
|
|
27
|
+
if (countFindings(loaded.findings).errors > 0) {
|
|
28
|
+
return invalidResult(loaded.files, loaded.findings);
|
|
29
|
+
}
|
|
30
|
+
const resolution = await this.resolve(loaded.assignments);
|
|
31
|
+
const findings = [...loaded.findings, ...resolution.findings];
|
|
32
|
+
if (countFindings(findings).errors > 0) {
|
|
33
|
+
return invalidResult(loaded.files, findings);
|
|
34
|
+
}
|
|
35
|
+
const actual = await this.org.currentAssignments(managedTargets(resolution));
|
|
36
|
+
const diff = diffAssignments(loaded.assignments, actual);
|
|
37
|
+
const { mode } = this.input;
|
|
38
|
+
const drift = {
|
|
39
|
+
adds: mode === 'destructive' ? diff.toAdd.length : 0,
|
|
40
|
+
updates: mode === 'destructive' ? diff.toUpdate.length : 0,
|
|
41
|
+
removes: mode === 'additive' ? diff.toRemove.length : 0,
|
|
42
|
+
};
|
|
43
|
+
return { files: loaded.files, findings, diff, drift, status: 'planned' };
|
|
44
|
+
}
|
|
45
|
+
/** Look every declared reference up in the org, returning findings and the target id maps. */
|
|
46
|
+
async resolve(assignments) {
|
|
47
|
+
const usernames = distinctAssignees(assignments);
|
|
48
|
+
const targetsByKind = kinds.map((kind) => ({ kind, targets: distinctTargets(assignments, kind) }));
|
|
49
|
+
const usersTask = usernames.length > 0 ? this.org.findUsers(usernames) : Promise.resolve([]);
|
|
50
|
+
const targetsTask = Promise.all(targetsByKind.map(({ kind, targets }) => (targets.length > 0 ? this.org.findTargets(kind, targets) : Promise.resolve([])).then((found) => ({ kind, targets, found }))));
|
|
51
|
+
const [foundUsers, perKind] = await Promise.all([usersTask, targetsTask]);
|
|
52
|
+
const findings = [...evaluateUsers(usernames, foundUsers)];
|
|
53
|
+
const targetIds = {};
|
|
54
|
+
for (const { kind, targets, found } of perKind) {
|
|
55
|
+
findings.push(...evaluateTargets(kind, targets, found.map((target) => target.name)));
|
|
56
|
+
targetIds[kind] = indexTargetsById(found);
|
|
57
|
+
}
|
|
58
|
+
return { findings, targetIds };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** The resolved ids of every declared target, to fetch their current memberships. */
|
|
62
|
+
function managedTargets(resolution) {
|
|
63
|
+
const refs = [];
|
|
64
|
+
for (const kind of kinds) {
|
|
65
|
+
for (const id of resolution.targetIds[kind].values()) {
|
|
66
|
+
refs.push({ kind, id });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return refs;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/services/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DesiredAssignment } from '../core/model.js';
|
|
2
|
+
import { Finding } from '../core/finding.js';
|
|
3
|
+
import { OrgClient } from './adapters/org-client.js';
|
|
4
|
+
export type ValidateResult = {
|
|
5
|
+
files: string[];
|
|
6
|
+
assignments: DesiredAssignment[];
|
|
7
|
+
findings: Finding[];
|
|
8
|
+
errors: number;
|
|
9
|
+
warnings: number;
|
|
10
|
+
failed: boolean;
|
|
11
|
+
};
|
|
12
|
+
/** Online validate: run the offline load, then resolve every reference against the org. */
|
|
13
|
+
export declare class ValidateService {
|
|
14
|
+
private readonly org;
|
|
15
|
+
private readonly files;
|
|
16
|
+
constructor(org: OrgClient, files: string[]);
|
|
17
|
+
run(): Promise<ValidateResult>;
|
|
18
|
+
/** Look every reference up in the org (in parallel) and evaluate the results. */
|
|
19
|
+
private resolve;
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { loadFiles } from '../core/load.js';
|
|
2
|
+
import { kinds, distinctAssignees, distinctTargets, evaluateUsers, evaluateTargets } from '../core/resolve.js';
|
|
3
|
+
import { countFindings } from '../core/finding.js';
|
|
4
|
+
/** Online validate: run the offline load, then resolve every reference against the org. */
|
|
5
|
+
export class ValidateService {
|
|
6
|
+
org;
|
|
7
|
+
files;
|
|
8
|
+
constructor(org, files) {
|
|
9
|
+
this.org = org;
|
|
10
|
+
this.files = files;
|
|
11
|
+
}
|
|
12
|
+
async run() {
|
|
13
|
+
const loaded = await loadFiles(this.files);
|
|
14
|
+
const online = await this.resolve(loaded.assignments);
|
|
15
|
+
const findings = [...loaded.findings, ...online];
|
|
16
|
+
const { errors, warnings } = countFindings(findings);
|
|
17
|
+
return {
|
|
18
|
+
files: loaded.files,
|
|
19
|
+
assignments: loaded.assignments,
|
|
20
|
+
findings,
|
|
21
|
+
errors,
|
|
22
|
+
warnings,
|
|
23
|
+
failed: errors > 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Look every reference up in the org (in parallel) and evaluate the results. */
|
|
27
|
+
async resolve(assignments) {
|
|
28
|
+
const tasks = [];
|
|
29
|
+
const usernames = distinctAssignees(assignments);
|
|
30
|
+
if (usernames.length > 0) {
|
|
31
|
+
tasks.push(this.org.findUsers(usernames).then((found) => evaluateUsers(usernames, found)));
|
|
32
|
+
}
|
|
33
|
+
for (const kind of kinds) {
|
|
34
|
+
const targets = distinctTargets(assignments, kind);
|
|
35
|
+
if (targets.length > 0) {
|
|
36
|
+
tasks.push(this.org.findTargets(kind, targets).then((found) => evaluateTargets(kind, targets, found.map((target) => target.name))));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const results = await Promise.all(tasks);
|
|
40
|
+
return results.flat();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/services/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE/G,OAAO,EAAW,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAY5D,2FAA2F;AAC3F,MAAM,OAAO,eAAe;IACY;IAAiC;IAArE,YAAoC,GAAc,EAAmB,KAAe;QAAhD,QAAG,GAAH,GAAG,CAAW;QAAmB,UAAK,GAAL,KAAK,CAAU;IAAG,CAAC;IAEjF,KAAK,CAAC,GAAG;QACZ,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAEtD,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,GAAG,MAAM,CAAC,CAAC;QACjD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QAErD,OAAO;YACH,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,QAAQ;YACR,MAAM;YACN,QAAQ;YACR,MAAM,EAAE,MAAM,GAAG,CAAC;SACrB,CAAC;IACN,CAAC;IAED,iFAAiF;IACzE,KAAK,CAAC,OAAO,CAAC,WAAgC;QAClD,MAAM,KAAK,GAA8B,EAAE,CAAC;QAE5C,MAAM,SAAS,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACjD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/F,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,eAAe,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACnD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,KAAK,CAAC,IAAI,CACN,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAC/C,eAAe,CACX,IAAI,EACJ,OAAO,EACP,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CACrC,CACJ,CACJ,CAAC;YACN,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzC,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;CACJ"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# summary
|
|
2
|
+
|
|
3
|
+
Reconcile a target org to match the permission set assignment files.
|
|
4
|
+
|
|
5
|
+
# description
|
|
6
|
+
|
|
7
|
+
Load the files, resolve every user and target against the org, diff the desired state against what the org currently has, then add, update expirations, and/or remove assignments per the mode. Additions, expiration updates, and removals run through the sObject Collections API with partial success, so one bad record does not roll back the rest. Deletions are capped by --max-deletes and confirmed before they run. Run validate and a --dry-run first.
|
|
8
|
+
|
|
9
|
+
# flags.file.summary
|
|
10
|
+
|
|
11
|
+
YAML file or glob to apply. Repeatable.
|
|
12
|
+
|
|
13
|
+
# flags.mode.summary
|
|
14
|
+
|
|
15
|
+
Which half of the reconcile to run: additive adds missing assignments and updates expirations, destructive removes only, sync does both.
|
|
16
|
+
|
|
17
|
+
# flags.max-deletes.summary
|
|
18
|
+
|
|
19
|
+
Abort before any change if the run would remove more than this many assignments.
|
|
20
|
+
|
|
21
|
+
# flags.dry-run.summary
|
|
22
|
+
|
|
23
|
+
Resolve and diff, print the plan, and change nothing.
|
|
24
|
+
|
|
25
|
+
# flags.no-prompt.summary
|
|
26
|
+
|
|
27
|
+
Skip the deletion confirmation prompt. Required to delete in JSON or other non-interactive runs.
|
|
28
|
+
|
|
29
|
+
# confirm.delete
|
|
30
|
+
|
|
31
|
+
This will remove %s assignment(s) from the org. Continue?
|
|
32
|
+
|
|
33
|
+
# summary.dryRun
|
|
34
|
+
|
|
35
|
+
Dry run: %s to add, %s to update, %s to remove. Nothing was changed.
|
|
36
|
+
|
|
37
|
+
# summary.applied
|
|
38
|
+
|
|
39
|
+
Applied: %s added, %s updated, %s removed.
|
|
40
|
+
|
|
41
|
+
# summary.declined
|
|
42
|
+
|
|
43
|
+
Aborted at the confirmation prompt. Nothing was changed.
|
|
44
|
+
|
|
45
|
+
# drift.note
|
|
46
|
+
|
|
47
|
+
%s change(s) the %s mode does not act on were skipped (drift). Run plan to see them.
|
|
48
|
+
|
|
49
|
+
# failure.line
|
|
50
|
+
|
|
51
|
+
failed to %s %s on %s: %s
|
|
52
|
+
|
|
53
|
+
# error.invalid
|
|
54
|
+
|
|
55
|
+
The files do not resolve cleanly against the org. Fix the errors above, then re-run.
|
|
56
|
+
|
|
57
|
+
# error.maxDeletes
|
|
58
|
+
|
|
59
|
+
Refusing to remove %s assignment(s): over the --max-deletes limit of %s. Raise the limit or narrow the change.
|
|
60
|
+
|
|
61
|
+
# error.promptInJson
|
|
62
|
+
|
|
63
|
+
Refusing to delete without confirmation in a non-interactive run. Re-run with --no-prompt.
|
|
64
|
+
|
|
65
|
+
# error.failed
|
|
66
|
+
|
|
67
|
+
Some changes failed. See the per-record errors above.
|
|
68
|
+
|
|
69
|
+
# examples
|
|
70
|
+
|
|
71
|
+
- Preview a full reconcile of the dev org without changing anything:
|
|
72
|
+
|
|
73
|
+
<%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org dev --mode sync --dry-run
|
|
74
|
+
|
|
75
|
+
- Grant any missing assignments (additive, the default):
|
|
76
|
+
|
|
77
|
+
<%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org dev
|
|
78
|
+
|
|
79
|
+
- Full reconcile of production in CI, without prompts:
|
|
80
|
+
|
|
81
|
+
<%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org prod --mode sync --no-prompt
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# summary
|
|
2
|
+
|
|
3
|
+
Statically check permission set assignment files, with no org connection.
|
|
4
|
+
|
|
5
|
+
# description
|
|
6
|
+
|
|
7
|
+
Parse and validate the YAML files offline. Reports invalid YAML, schema violations, duplicate keys, duplicate targets, and empty entries. Run it in a pre-commit hook or in CI before validate, plan, or apply.
|
|
8
|
+
|
|
9
|
+
# flags.file.summary
|
|
10
|
+
|
|
11
|
+
YAML file or glob to check. Repeatable.
|
|
12
|
+
|
|
13
|
+
# flags.strict.summary
|
|
14
|
+
|
|
15
|
+
Treat warnings as errors.
|
|
16
|
+
|
|
17
|
+
# summary.counts
|
|
18
|
+
|
|
19
|
+
%s errors, %s warnings.
|
|
20
|
+
|
|
21
|
+
# error.failed
|
|
22
|
+
|
|
23
|
+
Check found problems. See the output above.
|
|
24
|
+
|
|
25
|
+
# examples
|
|
26
|
+
|
|
27
|
+
- Check every file under permissions:
|
|
28
|
+
|
|
29
|
+
<%= config.bin %> <%= command.id %> --file "permissions/*.yml"
|
|
30
|
+
|
|
31
|
+
- Check specific files:
|
|
32
|
+
|
|
33
|
+
<%= config.bin %> <%= command.id %> --file permissions/sales.yml --file permissions/service.yml
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# summary
|
|
2
|
+
|
|
3
|
+
Generate a YAML file from the current org's permission set assignments.
|
|
4
|
+
|
|
5
|
+
# description
|
|
6
|
+
|
|
7
|
+
Query the target org for every assignable permission set, group, and license assignment held by active users and write them to a single user-keyed YAML file. The result is valid input for check, validate, plan, and apply, so it is a read-only way to bootstrap adoption from an org's current state. Profile-owned permission sets and inactive users are skipped.
|
|
8
|
+
|
|
9
|
+
# flags.output-file.summary
|
|
10
|
+
|
|
11
|
+
Path of the YAML file to write. Created (and its parent directories) if missing, overwritten if present.
|
|
12
|
+
|
|
13
|
+
# success
|
|
14
|
+
|
|
15
|
+
Exported %s assignments across %s users to %s.
|
|
16
|
+
|
|
17
|
+
# examples
|
|
18
|
+
|
|
19
|
+
- Export the dev org's assignments to permissions.yml:
|
|
20
|
+
|
|
21
|
+
<%= config.bin %> <%= command.id %> --target-org dev --output-file permissions.yml
|
|
22
|
+
|
|
23
|
+
- Export a production org into an environment folder:
|
|
24
|
+
|
|
25
|
+
<%= config.bin %> <%= command.id %> --target-org prod --output-file permissions/prod.yml
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# summary
|
|
2
|
+
|
|
3
|
+
Preview the changes that would reconcile a target org to the assignment files.
|
|
4
|
+
|
|
5
|
+
# description
|
|
6
|
+
|
|
7
|
+
Load the files, resolve every user and target against the org, fetch the org's current assignments, and diff the desired state against them. Read-only: it queries the org but never changes it, so it is the apply pipeline stopping before any DML. The full picture (assignments to add and would-be removes) is always shown regardless of mode, and whatever the chosen mode would not act on is surfaced as drift. Run it before apply to preview what would change.
|
|
8
|
+
|
|
9
|
+
# flags.file.summary
|
|
10
|
+
|
|
11
|
+
YAML file or glob to plan. Repeatable.
|
|
12
|
+
|
|
13
|
+
# flags.mode.summary
|
|
14
|
+
|
|
15
|
+
Which half of the reconcile to preview: additive adds and updates expirations, destructive removes only, sync does both. Adds, expiration updates, and removes are always shown either way.
|
|
16
|
+
|
|
17
|
+
# summary.counts
|
|
18
|
+
|
|
19
|
+
Plan: %s to add, %s to update, %s to remove, %s unchanged.
|
|
20
|
+
|
|
21
|
+
# summary.next
|
|
22
|
+
|
|
23
|
+
Reviewed the plan? Apply it with the same files: sf ps apply --mode %s
|
|
24
|
+
|
|
25
|
+
# drift.note
|
|
26
|
+
|
|
27
|
+
%s change(s) the %s mode does not act on were surfaced as drift.
|
|
28
|
+
|
|
29
|
+
# error.invalid
|
|
30
|
+
|
|
31
|
+
The files do not resolve cleanly against the org. Fix the errors above, then re-run.
|
|
32
|
+
|
|
33
|
+
# examples
|
|
34
|
+
|
|
35
|
+
- Preview a full reconcile of the dev org:
|
|
36
|
+
|
|
37
|
+
<%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org dev --mode sync
|
|
38
|
+
|
|
39
|
+
- Preview only the additions the default additive run would make:
|
|
40
|
+
|
|
41
|
+
<%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org dev
|
|
42
|
+
|
|
43
|
+
- Preview a full reconcile of production before applying it:
|
|
44
|
+
|
|
45
|
+
<%= config.bin %> <%= command.id %> --file "permissions/*.yml" --target-org prod --mode sync
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# summary
|
|
2
|
+
|
|
3
|
+
Validate permission set assignment files against a target org.
|
|
4
|
+
|
|
5
|
+
# description
|
|
6
|
+
|
|
7
|
+
Run every offline check, then resolve each referenced user, permission set, group, and license against the org. Reports users that are missing or inactive and targets that are missing or not unique. Read-only: it queries the org but never changes it. Run it before plan or apply.
|
|
8
|
+
|
|
9
|
+
# flags.file.summary
|
|
10
|
+
|
|
11
|
+
YAML file or glob to validate. Repeatable.
|
|
12
|
+
|
|
13
|
+
# summary.counts
|
|
14
|
+
|
|
15
|
+
%s errors, %s warnings.
|
|
16
|
+
|
|
17
|
+
# error.failed
|
|
18
|
+
|
|
19
|
+
Validation found problems. See the output above.
|
|
20
|
+
|
|
21
|
+
# examples
|
|
22
|
+
|
|
23
|
+
- Validate every file under permissions against the dev org:
|
|
24
|
+
|
|
25
|
+
<%= config.bin %> <%= command.id %> --file "permissions/\*.yml" --target-org dev
|
|
26
|
+
|
|
27
|
+
- Validate specific files against a named org:
|
|
28
|
+
|
|
29
|
+
<%= config.bin %> <%= command.id %> --file permissions/sales.yml --file permissions/service.yml --target-org prod
|