sf-plugin-permission-sets 0.0.0-dev.7 → 0.0.0-dev.73

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.
Files changed (132) hide show
  1. package/LICENSE.md +28 -0
  2. package/README.md +311 -88
  3. package/lib/adapters/connection-org-client.d.ts +23 -0
  4. package/lib/adapters/connection-org-client.d.ts.map +1 -0
  5. package/lib/adapters/connection-org-client.js +370 -0
  6. package/lib/adapters/connection-org-client.js.map +1 -0
  7. package/lib/adapters/index.d.ts +2 -0
  8. package/lib/adapters/index.d.ts.map +1 -0
  9. package/lib/adapters/index.js +2 -0
  10. package/lib/adapters/index.js.map +1 -0
  11. package/lib/commands/ps/apply.d.ts +41 -0
  12. package/lib/commands/ps/apply.d.ts.map +1 -0
  13. package/lib/commands/ps/apply.js +179 -0
  14. package/lib/commands/ps/apply.js.map +1 -0
  15. package/lib/commands/ps/check.d.ts +21 -0
  16. package/lib/commands/ps/check.d.ts.map +1 -0
  17. package/lib/commands/ps/check.js +55 -0
  18. package/lib/commands/ps/check.js.map +1 -0
  19. package/lib/commands/ps/export.d.ts +26 -0
  20. package/lib/commands/ps/export.d.ts.map +1 -0
  21. package/lib/commands/ps/export.js +74 -0
  22. package/lib/commands/ps/export.js.map +1 -0
  23. package/lib/commands/ps/plan.d.ts +61 -0
  24. package/lib/commands/ps/plan.d.ts.map +1 -0
  25. package/lib/commands/ps/plan.js +193 -0
  26. package/lib/commands/ps/plan.js.map +1 -0
  27. package/lib/commands/ps/validate.d.ts +21 -0
  28. package/lib/commands/ps/validate.d.ts.map +1 -0
  29. package/lib/commands/ps/validate.js +56 -0
  30. package/lib/commands/ps/validate.js.map +1 -0
  31. package/lib/core/diff.d.ts +46 -0
  32. package/lib/core/diff.d.ts.map +1 -0
  33. package/lib/core/diff.js +118 -0
  34. package/lib/core/diff.js.map +1 -0
  35. package/lib/core/finding.d.ts +43 -0
  36. package/lib/core/finding.d.ts.map +1 -0
  37. package/lib/core/finding.js +76 -0
  38. package/lib/core/finding.js.map +1 -0
  39. package/lib/core/index.d.ts +11 -0
  40. package/lib/core/index.d.ts.map +1 -0
  41. package/lib/core/index.js +10 -0
  42. package/lib/core/index.js.map +1 -0
  43. package/lib/core/load.d.ts +4 -0
  44. package/lib/core/load.d.ts.map +1 -0
  45. package/lib/core/load.js +60 -0
  46. package/lib/core/load.js.map +1 -0
  47. package/lib/core/model.d.ts +77 -0
  48. package/lib/core/model.d.ts.map +1 -0
  49. package/lib/core/model.js +2 -0
  50. package/lib/core/model.js.map +1 -0
  51. package/lib/core/normalize.d.ts +17 -0
  52. package/lib/core/normalize.d.ts.map +1 -0
  53. package/lib/core/normalize.js +74 -0
  54. package/lib/core/normalize.js.map +1 -0
  55. package/lib/core/parse.d.ts +10 -0
  56. package/lib/core/parse.d.ts.map +1 -0
  57. package/lib/core/parse.js +20 -0
  58. package/lib/core/parse.js.map +1 -0
  59. package/lib/core/report.d.ts +16 -0
  60. package/lib/core/report.d.ts.map +1 -0
  61. package/lib/core/report.js +127 -0
  62. package/lib/core/report.js.map +1 -0
  63. package/lib/core/resolve.d.ts +22 -0
  64. package/lib/core/resolve.d.ts.map +1 -0
  65. package/lib/core/resolve.js +92 -0
  66. package/lib/core/resolve.js.map +1 -0
  67. package/lib/core/schema.d.ts +23 -0
  68. package/lib/core/schema.d.ts.map +1 -0
  69. package/lib/core/schema.js +35 -0
  70. package/lib/core/schema.js.map +1 -0
  71. package/lib/core/serialize.d.ts +9 -0
  72. package/lib/core/serialize.d.ts.map +1 -0
  73. package/lib/core/serialize.js +50 -0
  74. package/lib/core/serialize.js.map +1 -0
  75. package/lib/core/target-name.d.ts +20 -0
  76. package/lib/core/target-name.d.ts.map +1 -0
  77. package/lib/core/target-name.js +32 -0
  78. package/lib/core/target-name.js.map +1 -0
  79. package/lib/core/username.d.ts +23 -0
  80. package/lib/core/username.d.ts.map +1 -0
  81. package/lib/core/username.js +35 -0
  82. package/lib/core/username.js.map +1 -0
  83. package/lib/index.d.ts +1 -0
  84. package/lib/index.d.ts.map +1 -0
  85. package/lib/services/adapters/index.d.ts +2 -0
  86. package/lib/services/adapters/index.d.ts.map +1 -0
  87. package/lib/services/adapters/index.js +2 -0
  88. package/lib/services/adapters/index.js.map +1 -0
  89. package/lib/services/adapters/org-client.d.ts +26 -0
  90. package/lib/services/adapters/org-client.d.ts.map +1 -0
  91. package/lib/services/adapters/org-client.js +2 -0
  92. package/lib/services/adapters/org-client.js.map +1 -0
  93. package/lib/services/apply.d.ts +33 -0
  94. package/lib/services/apply.d.ts.map +1 -0
  95. package/lib/services/apply.js +82 -0
  96. package/lib/services/apply.js.map +1 -0
  97. package/lib/services/check.d.ts +14 -0
  98. package/lib/services/check.d.ts.map +1 -0
  99. package/lib/services/check.js +18 -0
  100. package/lib/services/check.js.map +1 -0
  101. package/lib/services/export.d.ts +19 -0
  102. package/lib/services/export.d.ts.map +1 -0
  103. package/lib/services/export.js +30 -0
  104. package/lib/services/export.js.map +1 -0
  105. package/lib/services/index.d.ts +6 -0
  106. package/lib/services/index.d.ts.map +1 -0
  107. package/lib/services/index.js +6 -0
  108. package/lib/services/index.js.map +1 -0
  109. package/lib/services/plan.d.ts +22 -0
  110. package/lib/services/plan.d.ts.map +1 -0
  111. package/lib/services/plan.js +44 -0
  112. package/lib/services/plan.js.map +1 -0
  113. package/lib/services/resolution.d.ts +25 -0
  114. package/lib/services/resolution.d.ts.map +1 -0
  115. package/lib/services/resolution.js +75 -0
  116. package/lib/services/resolution.js.map +1 -0
  117. package/lib/services/validate.d.ts +22 -0
  118. package/lib/services/validate.d.ts.map +1 -0
  119. package/lib/services/validate.js +59 -0
  120. package/lib/services/validate.js.map +1 -0
  121. package/messages/ps.apply.md +85 -0
  122. package/messages/ps.check.md +33 -0
  123. package/messages/ps.export.md +45 -0
  124. package/messages/ps.plan.md +85 -0
  125. package/messages/ps.validate.md +29 -0
  126. package/package.json +119 -180
  127. package/lib/commands/ps/info.d.ts +0 -11
  128. package/lib/commands/ps/info.js +0 -17
  129. package/lib/commands/ps/info.js.map +0 -1
  130. package/messages/ps.info.md +0 -17
  131. package/oclif.lock +0 -7781
  132. package/oclif.manifest.json +0 -51
@@ -0,0 +1,127 @@
1
+ import { kindKeys } from './normalize.js';
2
+ /** Human labels for the section headers, so the plan reads as prose, not YAML keys. */
3
+ const kindLabels = {
4
+ permissionSet: 'Permission Sets',
5
+ permissionSetGroup: 'Permission Set Groups',
6
+ permissionSetLicense: 'Permission Set Licenses',
7
+ };
8
+ function bucketFor(byKind, kind, target) {
9
+ let byTarget = byKind.get(kind);
10
+ if (!byTarget) {
11
+ byTarget = new Map();
12
+ byKind.set(kind, byTarget);
13
+ }
14
+ let entry = byTarget.get(target.asKey());
15
+ if (!entry) {
16
+ const bucket = { adds: new Map(), updates: new Map(), removes: new Set(), unchanged: new Map() };
17
+ entry = { target, bucket };
18
+ byTarget.set(target.asKey(), entry);
19
+ }
20
+ return entry.bucket;
21
+ }
22
+ /**
23
+ * A canonical, readable form of an expiration instant, so values from the org and from the
24
+ * files display identically side by side. Falls back to the raw value if it cannot be parsed.
25
+ */
26
+ function canonicalExpiration(value) {
27
+ const parsed = Date.parse(value);
28
+ if (Number.isNaN(parsed))
29
+ return value;
30
+ const instant = new Date(parsed);
31
+ return instant.toISOString().replace(/\.\d{3}Z$/, 'Z');
32
+ }
33
+ /** An assignee line suffixed with its expiration when there is one. */
34
+ function withExpiry(assignee, expiration) {
35
+ if (!expiration)
36
+ return assignee;
37
+ return `${assignee} expires ${canonicalExpiration(expiration)}`;
38
+ }
39
+ /** A canonical expiration for display, with `never` standing in for no expiration. */
40
+ function expiryOrNever(expiration) {
41
+ if (!expiration)
42
+ return 'never';
43
+ return canonicalExpiration(expiration);
44
+ }
45
+ /** An update line showing the expiration transition, with `never` standing in for no expiration. */
46
+ function withTransition(assignee, previous, next) {
47
+ const from = expiryOrNever(previous);
48
+ const to = expiryOrNever(next);
49
+ return `${assignee} expires ${from} → ${to}`;
50
+ }
51
+ /** Group a diff into per-kind, per-target buckets, keeping only the operations the mode shows. */
52
+ function collectBuckets(diff, options) {
53
+ const showAdditive = options.mode !== 'destructive';
54
+ const showDestructive = options.mode !== 'additive';
55
+ const byKind = new Map();
56
+ if (showAdditive) {
57
+ for (const assignment of diff.toAdd) {
58
+ const bucket = bucketFor(byKind, assignment.kind, assignment.target);
59
+ bucket.adds.set(assignment.assignee.toString(), assignment.expiration);
60
+ }
61
+ for (const update of diff.toUpdate) {
62
+ const bucket = bucketFor(byKind, update.kind, update.target);
63
+ bucket.updates.set(update.assignee.toString(), {
64
+ previous: update.previousExpiration,
65
+ next: update.expiration,
66
+ });
67
+ }
68
+ }
69
+ if (showDestructive) {
70
+ for (const assignment of diff.toRemove) {
71
+ const bucket = bucketFor(byKind, assignment.kind, assignment.target);
72
+ bucket.removes.add(assignment.assignee.toString());
73
+ }
74
+ }
75
+ if (options.showUnchanged) {
76
+ for (const assignment of diff.unchanged) {
77
+ const bucket = bucketFor(byKind, assignment.kind, assignment.target);
78
+ bucket.unchanged.set(assignment.assignee.toString(), assignment.expiration);
79
+ }
80
+ }
81
+ return byKind;
82
+ }
83
+ /** The `+`/`~`/`-`/`=` lines for one target, each group sorted by assignee. Empty when nothing shows. */
84
+ function renderBucket(bucket) {
85
+ const entries = [];
86
+ for (const assignee of [...bucket.adds.keys()].sort()) {
87
+ entries.push(` + ${withExpiry(assignee, bucket.adds.get(assignee) ?? null)}`);
88
+ }
89
+ for (const assignee of [...bucket.updates.keys()].sort()) {
90
+ const change = bucket.updates.get(assignee);
91
+ entries.push(` ~ ${withTransition(assignee, change.previous, change.next)}`);
92
+ }
93
+ for (const assignee of [...bucket.removes].sort())
94
+ entries.push(` - ${assignee}`);
95
+ for (const assignee of [...bucket.unchanged.keys()].sort()) {
96
+ entries.push(` = ${withExpiry(assignee, bucket.unchanged.get(assignee) ?? null)}`);
97
+ }
98
+ return entries;
99
+ }
100
+ /**
101
+ * Render a diff as a plan body, grouped by kind then target, with `+` adds, `~` expiration
102
+ * updates, `-` removes, and `=` unchanged. The mode scopes which operations appear (additive
103
+ * hides removes, destructive hides adds and updates), unchanged lines appear only when asked,
104
+ * and targets with nothing to show are omitted. Shared by plan and apply.
105
+ */
106
+ export function formatDiff(diff, options) {
107
+ const byKind = collectBuckets(diff, options);
108
+ const lines = [];
109
+ for (const [kind] of kindKeys) {
110
+ const byTarget = byKind.get(kind);
111
+ if (!byTarget)
112
+ continue;
113
+ const sorted = [...byTarget.values()].sort((left, right) => left.target.toString().localeCompare(right.target.toString()));
114
+ const targetLines = [];
115
+ for (const { target, bucket } of sorted) {
116
+ const entries = renderBucket(bucket);
117
+ if (entries.length === 0)
118
+ continue;
119
+ targetLines.push(` ${target.toString()}`, ...entries);
120
+ }
121
+ if (targetLines.length === 0)
122
+ continue;
123
+ lines.push(kindLabels[kind], ...targetLines);
124
+ }
125
+ return lines;
126
+ }
127
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/core/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { DesiredAssignment, Kind, OrgTarget, OrgUser } from './model.js';
2
+ import { TargetName } from './target-name.js';
3
+ import { Username } from './username.js';
4
+ import { Finding } from './finding.js';
5
+ export declare const kinds: Kind[];
6
+ /** The distinct usernames assigned across all assignments. */
7
+ export declare function distinctAssignees(assignments: DesiredAssignment[]): Username[];
8
+ /** The distinct targets of one kind across all assignments. */
9
+ export declare function distinctTargets(assignments: DesiredAssignment[], kind: Kind): TargetName[];
10
+ /** Every declared user must exist in the org and be active. */
11
+ export declare function evaluateUsers(declared: Username[], found: OrgUser[]): Finding[];
12
+ /**
13
+ * Every declared target of one kind must resolve to exactly one record in the
14
+ * org. `found` is the list of matching identifiers the org returned; matching is
15
+ * case-insensitive, mirroring how the org compares them.
16
+ */
17
+ export declare function evaluateTargets(kind: Kind, declared: TargetName[], found: TargetName[]): Finding[];
18
+ /** Index active users by their comparison key to their org id, for building assignments. */
19
+ export declare function indexUsersById(found: OrgUser[]): Map<string, string>;
20
+ /** Index targets by their comparison key to their org id, skipping names that resolve ambiguously. */
21
+ export declare function indexTargetsById(found: OrgTarget[]): Map<string, string>;
22
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/core/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAmF,MAAM,cAAc,CAAC;AASxH,eAAO,MAAM,KAAK,EAA8B,IAAI,EAAE,CAAC;AAcvD,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,EAAE,GAAG,QAAQ,EAAE,CAE9E;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAAC,WAAW,EAAE,iBAAiB,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,UAAU,EAAE,CAI1F;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAgB/E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,CAkBlG;AAED,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQpE;AAED,sGAAsG;AACtG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAaxE"}
@@ -0,0 +1,92 @@
1
+ import { userNotFoundError, userInactiveError, targetNotFoundError, targetAmbiguousError } from './finding.js';
2
+ /** Human label per kind, used in findings. Domain wording, not SObject names. */
3
+ const kindLabels = {
4
+ permissionSet: 'permission set',
5
+ permissionSetGroup: 'permission set group',
6
+ permissionSetLicense: 'permission set license',
7
+ };
8
+ export const kinds = Object.keys(kindLabels);
9
+ /** De-duplicate identifiers by their comparison key, keeping the first spelling of each. */
10
+ function distinct(values) {
11
+ const byKey = new Map();
12
+ for (const value of values) {
13
+ if (!byKey.has(value.asKey())) {
14
+ byKey.set(value.asKey(), value);
15
+ }
16
+ }
17
+ return [...byKey.values()];
18
+ }
19
+ /** The distinct usernames assigned across all assignments. */
20
+ export function distinctAssignees(assignments) {
21
+ return distinct(assignments.map((assignment) => assignment.assignee));
22
+ }
23
+ /** The distinct targets of one kind across all assignments. */
24
+ export function distinctTargets(assignments, kind) {
25
+ return distinct(assignments.filter((assignment) => assignment.kind === kind).map((assignment) => assignment.target));
26
+ }
27
+ /** Every declared user must exist in the org and be active. */
28
+ export function evaluateUsers(declared, found) {
29
+ const byName = new Map();
30
+ for (const user of found) {
31
+ byName.set(user.username.asKey(), user);
32
+ }
33
+ const findings = [];
34
+ for (const username of declared) {
35
+ const user = byName.get(username.asKey());
36
+ if (!user) {
37
+ findings.push(userNotFoundError(username));
38
+ }
39
+ else if (!user.isActive) {
40
+ findings.push(userInactiveError(username));
41
+ }
42
+ }
43
+ return findings;
44
+ }
45
+ /**
46
+ * Every declared target of one kind must resolve to exactly one record in the
47
+ * org. `found` is the list of matching identifiers the org returned; matching is
48
+ * case-insensitive, mirroring how the org compares them.
49
+ */
50
+ export function evaluateTargets(kind, declared, found) {
51
+ const label = kindLabels[kind];
52
+ const counts = new Map();
53
+ for (const name of found) {
54
+ counts.set(name.asKey(), (counts.get(name.asKey()) ?? 0) + 1);
55
+ }
56
+ const findings = [];
57
+ for (const target of declared) {
58
+ const count = counts.get(target.asKey()) ?? 0;
59
+ if (count === 0) {
60
+ findings.push(targetNotFoundError(target, label));
61
+ }
62
+ else if (count > 1) {
63
+ findings.push(targetAmbiguousError(target, label));
64
+ }
65
+ }
66
+ return findings;
67
+ }
68
+ /** Index active users by their comparison key to their org id, for building assignments. */
69
+ export function indexUsersById(found) {
70
+ const byName = new Map();
71
+ for (const user of found) {
72
+ if (user.isActive) {
73
+ byName.set(user.username.asKey(), user.id);
74
+ }
75
+ }
76
+ return byName;
77
+ }
78
+ /** Index targets by their comparison key to their org id, skipping names that resolve ambiguously. */
79
+ export function indexTargetsById(found) {
80
+ const counts = new Map();
81
+ for (const target of found) {
82
+ counts.set(target.name.asKey(), (counts.get(target.name.asKey()) ?? 0) + 1);
83
+ }
84
+ const byName = new Map();
85
+ for (const target of found) {
86
+ if (counts.get(target.name.asKey()) === 1) {
87
+ byName.set(target.name.asKey(), target.id);
88
+ }
89
+ }
90
+ return byName;
91
+ }
92
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/core/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ import { Finding } from './finding.js';
3
+ declare const fileSchema: z.ZodObject<{
4
+ users: z.ZodRecord<z.ZodString, z.ZodObject<{
5
+ permissionSets: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
6
+ name: z.ZodString;
7
+ expiration: z.ZodISODateTime;
8
+ }, z.core.$strict>]>>>;
9
+ permissionSetGroups: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
10
+ name: z.ZodString;
11
+ expiration: z.ZodISODateTime;
12
+ }, z.core.$strict>]>>>;
13
+ permissionSetLicenses: z.ZodOptional<z.ZodArray<z.ZodString>>;
14
+ }, z.core.$strict>>;
15
+ }, z.core.$strict>;
16
+ export type FileShape = z.infer<typeof fileSchema>;
17
+ /** Validate a parsed object against the file contract, turning issues into findings. */
18
+ export declare function validateFile(data: unknown, file: string): {
19
+ data?: FileShape;
20
+ findings: Finding[];
21
+ };
22
+ export {};
23
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAe,MAAM,cAAc,CAAC;AA4BpD,QAAA,MAAM,UAAU;;;;;;;;;;;;kBAEd,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEnD,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,CAUnG"}
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+ import { schemaError } from './finding.js';
3
+ // The permission set name
4
+ const name = z.string().min(1);
5
+ // The permission set expiration date
6
+ const expiration = z.iso.datetime({ offset: true });
7
+ /** An entry is either a bare target name or a name with an expiration. */
8
+ const expiringList = z.array(z.union([
9
+ name,
10
+ z.strictObject({
11
+ name,
12
+ expiration,
13
+ }),
14
+ ]));
15
+ /** Licenses cannot expire (PermissionSetLicenseAssign has no ExpirationDate), so names only. */
16
+ const plainList = z.array(name);
17
+ const userEntrySchema = z.strictObject({
18
+ permissionSets: expiringList.optional(),
19
+ permissionSetGroups: expiringList.optional(),
20
+ permissionSetLicenses: plainList.optional(),
21
+ });
22
+ const fileSchema = z.strictObject({
23
+ users: z.record(z.string().min(1), userEntrySchema),
24
+ });
25
+ /** Validate a parsed object against the file contract, turning issues into findings. */
26
+ export function validateFile(data, file) {
27
+ const parsed = fileSchema.safeParse(data);
28
+ if (parsed.success) {
29
+ return { data: parsed.data, findings: [] };
30
+ }
31
+ return {
32
+ findings: parsed.error.issues.map((issue) => schemaError(issue.path.join('.') || '(root)', issue.message, file)),
33
+ };
34
+ }
35
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAW,WAAW,EAAE,MAAM,cAAc,CAAC;AAEpD,0BAA0B;AAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAE/B,qCAAqC;AACrC,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAEpD,0EAA0E;AAC1E,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CACxB,CAAC,CAAC,KAAK,CAAC;IACJ,IAAI;IACJ,CAAC,CAAC,YAAY,CAAC;QACX,IAAI;QACJ,UAAU;KACb,CAAC;CACL,CAAC,CACL,CAAC;AAEF,gGAAgG;AAChG,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAEhC,MAAM,eAAe,GAAG,CAAC,CAAC,YAAY,CAAC;IACnC,cAAc,EAAE,YAAY,CAAC,QAAQ,EAAE;IACvC,mBAAmB,EAAE,YAAY,CAAC,QAAQ,EAAE;IAC5C,qBAAqB,EAAE,SAAS,CAAC,QAAQ,EAAE;CAC9C,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC;IAC9B,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC;CACtD,CAAC,CAAC;AAIH,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAAC,IAAa,EAAE,IAAY;IACpD,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC/C,CAAC;IACD,OAAO;QACH,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACxC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CACrE;KACJ,CAAC;AACN,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { DesiredAssignment } from './model.js';
2
+ /**
3
+ * Emit canonical assignments back to a user-keyed YAML document: the inverse of
4
+ * normalize. Usernames and targets are sorted and de-duplicated so the output is
5
+ * deterministic, empty scopes are omitted, and an assignment with an expiration
6
+ * is written as the object form so it round-trips through the schema.
7
+ */
8
+ export declare function serializeAssignments(assignments: DesiredAssignment[]): string;
9
+ //# sourceMappingURL=serialize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/core/serialize.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAuC/C;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAuB7E"}
@@ -0,0 +1,50 @@
1
+ import { stringify } from 'yaml';
2
+ import { kindKeys } from './normalize.js';
3
+ import { distinctAssignees } from './resolve.js';
4
+ /** Order identifiers by their text, matching the plain string sort the output relies on. */
5
+ function byText(left, right) {
6
+ const leftText = left.toString();
7
+ const rightText = right.toString();
8
+ if (leftText === rightText)
9
+ return 0;
10
+ return leftText < rightText ? -1 : 1;
11
+ }
12
+ /** One user's entries for one scope, de-duplicated by target and ordered by name. */
13
+ function scopeEntries(assignments) {
14
+ const byTarget = new Map();
15
+ for (const assignment of assignments) {
16
+ if (!byTarget.has(assignment.target.asKey())) {
17
+ byTarget.set(assignment.target.asKey(), { target: assignment.target, expiration: assignment.expiration });
18
+ }
19
+ }
20
+ const sorted = [...byTarget.values()].sort((left, right) => byText(left.target, right.target));
21
+ return sorted.map(({ target, expiration }) => {
22
+ if (!expiration)
23
+ return target.toString();
24
+ return { name: target.toString(), expiration };
25
+ });
26
+ }
27
+ /**
28
+ * Emit canonical assignments back to a user-keyed YAML document: the inverse of
29
+ * normalize. Usernames and targets are sorted and de-duplicated so the output is
30
+ * deterministic, empty scopes are omitted, and an assignment with an expiration
31
+ * is written as the object form so it round-trips through the schema.
32
+ */
33
+ export function serializeAssignments(assignments) {
34
+ const assignees = distinctAssignees(assignments);
35
+ const usernames = assignees.sort(byText);
36
+ const users = {};
37
+ for (const username of usernames) {
38
+ const entry = {};
39
+ for (const [kind, key] of kindKeys) {
40
+ const matching = assignments.filter((assignment) => assignment.assignee.equals(username) && assignment.kind === kind);
41
+ const entries = scopeEntries(matching);
42
+ if (entries.length > 0) {
43
+ entry[key] = entries;
44
+ }
45
+ }
46
+ users[username.toString()] = entry;
47
+ }
48
+ return stringify({ users });
49
+ }
50
+ //# sourceMappingURL=serialize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.js","sourceRoot":"","sources":["../../src/core/serialize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,EAAE,QAAQ,EAAY,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AASjD,4FAA4F;AAC5F,SAAS,MAAM,CAAC,IAA4B,EAAE,KAA6B;IACvE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;IACjC,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;IAEnC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC;IAErC,OAAO,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,qFAAqF;AACrF,SAAS,YAAY,CAAC,WAAgC;IAClD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA6D,CAAC;IACtF,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC;YAC3C,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC;QAC9G,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAE/F,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE;QACzC,IAAI,CAAC,UAAU;YAAE,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;QAE1C,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,WAAgC;IACjE,MAAM,SAAS,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,KAAK,GAAwB,EAAE,CAAC;IAEtC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAgC,EAAE,CAAC;QAE9C,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,QAAQ,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAC/B,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,IAAI,KAAK,IAAI,CACnF,CAAC;YACF,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YAEvC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;YACzB,CAAC;QACL,CAAC;QAED,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,KAAK,CAAC;IACvC,CAAC;IAED,OAAO,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The API name of a permission set, group, or license. An org compares these without
3
+ * regard to case, so the model compares, indexes, and de-duplicates by `key` and keeps
4
+ * the text as written only for display and for round-tripping back to YAML.
5
+ *
6
+ * See Username for why these are two classes rather than one with a shared base.
7
+ */
8
+ export declare class TargetName {
9
+ private readonly raw;
10
+ private constructor();
11
+ static of(value: string): TargetName;
12
+ /** The value to compare, index, and de-duplicate by. */
13
+ asKey(): string;
14
+ equals(other: TargetName): boolean;
15
+ /** The name as written, for display and for messages. */
16
+ toString(): string;
17
+ /** Keeps `--json` payloads plain strings, spelled as the files and the org spell them. */
18
+ toJSON(): string;
19
+ }
20
+ //# sourceMappingURL=target-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target-name.d.ts","sourceRoot":"","sources":["../../src/core/target-name.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,UAAU;IACC,OAAO,CAAC,QAAQ,CAAC,GAAG;IAAxC,OAAO;WAEO,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU;IAI3C,wDAAwD;IACjD,KAAK,IAAI,MAAM;IAIf,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO;IAIzC,yDAAyD;IAClD,QAAQ,IAAI,MAAM;IAIzB,0FAA0F;IACnF,MAAM,IAAI,MAAM;CAG1B"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The API name of a permission set, group, or license. An org compares these without
3
+ * regard to case, so the model compares, indexes, and de-duplicates by `key` and keeps
4
+ * the text as written only for display and for round-tripping back to YAML.
5
+ *
6
+ * See Username for why these are two classes rather than one with a shared base.
7
+ */
8
+ export class TargetName {
9
+ raw;
10
+ constructor(raw) {
11
+ this.raw = raw;
12
+ }
13
+ static of(value) {
14
+ return new TargetName(value);
15
+ }
16
+ /** The value to compare, index, and de-duplicate by. */
17
+ asKey() {
18
+ return this.raw.toLowerCase();
19
+ }
20
+ equals(other) {
21
+ return this.asKey() === other.asKey();
22
+ }
23
+ /** The name as written, for display and for messages. */
24
+ toString() {
25
+ return this.raw;
26
+ }
27
+ /** Keeps `--json` payloads plain strings, spelled as the files and the org spell them. */
28
+ toJSON() {
29
+ return this.raw;
30
+ }
31
+ }
32
+ //# sourceMappingURL=target-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target-name.js","sourceRoot":"","sources":["../../src/core/target-name.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,OAAO,UAAU;IACkB;IAArC,YAAqC,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAE7C,MAAM,CAAC,EAAE,CAAC,KAAa;QAC1B,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,wDAAwD;IACjD,KAAK;QACR,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;IAClC,CAAC;IAEM,MAAM,CAAC,KAAiB;QAC3B,OAAO,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC;IAC1C,CAAC;IAED,yDAAyD;IAClD,QAAQ;QACX,OAAO,IAAI,CAAC,GAAG,CAAC;IACpB,CAAC;IAED,0FAA0F;IACnF,MAAM;QACT,OAAO,IAAI,CAAC,GAAG,CAAC;IACpB,CAAC;CACJ"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A Salesforce username. An org compares usernames without regard to case, so every
3
+ * comparison, index, and de-duplication in the model goes through `key` rather than the
4
+ * raw text, and the text as written survives only for display and for round-tripping
5
+ * back to YAML.
6
+ *
7
+ * Deliberately not sharing a base class with TargetName: the private field is what makes
8
+ * the two nominally distinct to the compiler, so one cannot be passed where the other is
9
+ * expected. A shared base would make them structurally identical again.
10
+ */
11
+ export declare class Username {
12
+ private readonly raw;
13
+ private constructor();
14
+ static of(value: string): Username;
15
+ /** The value to compare, index, and de-duplicate by. */
16
+ asKey(): string;
17
+ equals(other: Username): boolean;
18
+ /** The username as written, for display and for messages. */
19
+ toString(): string;
20
+ /** Keeps `--json` payloads plain strings, spelled as the files and the org spell them. */
21
+ toJSON(): string;
22
+ }
23
+ //# sourceMappingURL=username.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"username.d.ts","sourceRoot":"","sources":["../../src/core/username.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,QAAQ;IACG,OAAO,CAAC,QAAQ,CAAC,GAAG;IAAxC,OAAO;WAEO,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ;IAIzC,wDAAwD;IACjD,KAAK,IAAI,MAAM;IAIf,MAAM,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO;IAIvC,6DAA6D;IACtD,QAAQ,IAAI,MAAM;IAIzB,0FAA0F;IACnF,MAAM,IAAI,MAAM;CAG1B"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A Salesforce username. An org compares usernames without regard to case, so every
3
+ * comparison, index, and de-duplication in the model goes through `key` rather than the
4
+ * raw text, and the text as written survives only for display and for round-tripping
5
+ * back to YAML.
6
+ *
7
+ * Deliberately not sharing a base class with TargetName: the private field is what makes
8
+ * the two nominally distinct to the compiler, so one cannot be passed where the other is
9
+ * expected. A shared base would make them structurally identical again.
10
+ */
11
+ export class Username {
12
+ raw;
13
+ constructor(raw) {
14
+ this.raw = raw;
15
+ }
16
+ static of(value) {
17
+ return new Username(value);
18
+ }
19
+ /** The value to compare, index, and de-duplicate by. */
20
+ asKey() {
21
+ return this.raw.toLowerCase();
22
+ }
23
+ equals(other) {
24
+ return this.asKey() === other.asKey();
25
+ }
26
+ /** The username as written, for display and for messages. */
27
+ toString() {
28
+ return this.raw;
29
+ }
30
+ /** Keeps `--json` payloads plain strings, spelled as the files and the org spell them. */
31
+ toJSON() {
32
+ return this.raw;
33
+ }
34
+ }
35
+ //# sourceMappingURL=username.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"username.js","sourceRoot":"","sources":["../../src/core/username.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,OAAO,QAAQ;IACoB;IAArC,YAAqC,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAE7C,MAAM,CAAC,EAAE,CAAC,KAAa;QAC1B,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,wDAAwD;IACjD,KAAK;QACR,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;IAClC,CAAC;IAEM,MAAM,CAAC,KAAe;QACzB,OAAO,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC;IAC1C,CAAC;IAED,6DAA6D;IACtD,QAAQ;QACX,OAAO,IAAI,CAAC,GAAG,CAAC;IACpB,CAAC;IAED,0FAA0F;IACnF,MAAM;QACT,OAAO,IAAI,CAAC,GAAG,CAAC;IACpB,CAAC;CACJ"}
package/lib/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  declare const _default: {};
2
2
  export default _default;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,wBAAkB"}
@@ -0,0 +1,2 @@
1
+ export { OrgClient } from './org-client.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/adapters/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ import { ActualAssignment, AssignmentFilter, AssignmentOutcome, AssignmentUpdate, DesiredAssignment, OrgTarget, OrgUser, ResolvedAddition, TargetName, TargetRef, Username } from '../../core/index.js';
2
+ /**
3
+ * Port: the org operations a service needs, in domain terms. Declared here, by the
4
+ * consumer, so services depend on the abstraction and the adapter implements it.
5
+ */
6
+ export interface OrgClient {
7
+ /** The users that exist in the org, among the given usernames. */
8
+ findUsers(usernames: Username[]): Promise<OrgUser[]>;
9
+ /** The permission sets (with ids) that exist in the org, among the given names. */
10
+ findPermissionSets(names: TargetName[]): Promise<OrgTarget[]>;
11
+ /** The permission set groups (with ids) that exist in the org, among the given developer names. */
12
+ findPermissionSetGroups(names: TargetName[]): Promise<OrgTarget[]>;
13
+ /** The permission set licenses (with ids) that exist in the org, among the given developer names. */
14
+ findPermissionSetLicenses(names: TargetName[]): Promise<OrgTarget[]>;
15
+ /** Every assignable permission set, group, and license assignment held by active users, narrowed by the filter. */
16
+ listAssignments(filter?: AssignmentFilter): Promise<DesiredAssignment[]>;
17
+ /** The current assignments of the given managed targets, with their record ids. */
18
+ listCurrentAssignments(targets: TargetRef[]): Promise<ActualAssignment[]>;
19
+ /** Insert the given assignments, reporting per-record success or failure. */
20
+ addAssignments(additions: ResolvedAddition[]): Promise<AssignmentOutcome[]>;
21
+ /** Update the expiration of the given assignments, reporting per-record success or failure. */
22
+ updateAssignments(updates: AssignmentUpdate[]): Promise<AssignmentOutcome[]>;
23
+ /** Delete the given assignments by record id, reporting per-record success or failure. */
24
+ removeAssignments(removals: ActualAssignment[]): Promise<AssignmentOutcome[]>;
25
+ }
26
+ //# sourceMappingURL=org-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-client.d.ts","sourceRoot":"","sources":["../../../src/services/adapters/org-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,OAAO,EACP,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,QAAQ,EACX,MAAM,qBAAqB,CAAC;AAE7B;;;GAGG;AACH,MAAM,WAAW,SAAS;IACtB,kEAAkE;IAClE,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACrD,mFAAmF;IACnF,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAC9D,mGAAmG;IACnG,uBAAuB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACnE,qGAAqG;IACrG,yBAAyB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACrE,mHAAmH;IACnH,eAAe,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;IACzE,mFAAmF;IACnF,sBAAsB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC1E,6EAA6E;IAC7E,cAAc,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;IAC5E,+FAA+F;IAC/F,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;IAC7E,0FAA0F;IAC1F,iBAAiB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;CACjF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=org-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-client.js","sourceRoot":"","sources":["../../../src/services/adapters/org-client.ts"],"names":[],"mappings":""}
@@ -0,0 +1,33 @@
1
+ import { AssignmentOutcome, Diff, Finding } from '../core/index.js';
2
+ import { OrgClient } from './adapters/index.js';
3
+ export type ApplyMode = 'additive' | 'destructive' | 'sync';
4
+ /** How the service asks its caller to approve a destructive batch before applying it. */
5
+ export type ConfirmDeletions = (count: number) => Promise<boolean>;
6
+ export type ApplyInput = {
7
+ mode: ApplyMode;
8
+ maxDeletes: number;
9
+ dryRun: boolean;
10
+ };
11
+ /** How a run ended, so the command can report and set the exit code. */
12
+ export type ApplyStatus = 'applied' | 'dry-run' | 'declined' | 'max-deletes-exceeded' | 'invalid';
13
+ export type ApplyResult = {
14
+ files: string[];
15
+ findings: Finding[];
16
+ diff: Diff;
17
+ outcomes: AssignmentOutcome[];
18
+ status: ApplyStatus;
19
+ failed: boolean;
20
+ };
21
+ /**
22
+ * Load the files, resolve every reference to an org id, diff against the org's
23
+ * current state, then add and/or remove per the mode. Deletions are capped by
24
+ * maxDeletes and gated by an injected confirmation.
25
+ */
26
+ export declare class ApplyService {
27
+ private readonly org;
28
+ private readonly confirmDeletions;
29
+ constructor(org: OrgClient, confirmDeletions: ConfirmDeletions);
30
+ run(files: string[], input: ApplyInput): Promise<ApplyResult>;
31
+ private executeResolved;
32
+ }
33
+ //# sourceMappingURL=apply.d.ts.map