sf-plugin-permission-sets 0.0.0-dev.10 → 0.0.0-dev.12
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 +37 -76
- package/lib/commands/ps/check.d.ts +18 -0
- package/lib/commands/ps/check.js +45 -0
- package/lib/commands/ps/check.js.map +1 -0
- package/lib/core/load.d.ts +8 -0
- package/lib/core/load.js +52 -0
- package/lib/core/load.js.map +1 -0
- package/lib/core/model.d.ts +19 -0
- package/lib/core/model.js +2 -0
- package/lib/core/model.js.map +1 -0
- package/lib/core/normalize.d.ts +10 -0
- package/lib/core/normalize.js +45 -0
- package/lib/core/normalize.js.map +1 -0
- package/lib/core/parse.d.ts +9 -0
- package/lib/core/parse.js +25 -0
- package/lib/core/parse.js.map +1 -0
- package/lib/core/report.d.ts +8 -0
- package/lib/core/report.js +15 -0
- package/lib/core/report.js.map +1 -0
- package/lib/core/schema.d.ts +20 -0
- package/lib/core/schema.js +26 -0
- package/lib/core/schema.js.map +1 -0
- package/lib/services/check.d.ts +17 -0
- package/lib/services/check.js +20 -0
- package/lib/services/check.js.map +1 -0
- package/messages/ps.check.md +33 -0
- package/oclif.lock +570 -1
- package/oclif.manifest.json +63 -1
- package/package.json +12 -24
package/README.md
CHANGED
|
@@ -19,7 +19,6 @@ Stop clicking through Setup to grant access. Commit a YAML file, open a PR, let
|
|
|
19
19
|
- [Validations](#validations)
|
|
20
20
|
- [Modes](#modes)
|
|
21
21
|
- [Commands](#commands)
|
|
22
|
-
- [CI/CD](#cicd)
|
|
23
22
|
- [Inspiration & equivalents](#inspiration--equivalents)
|
|
24
23
|
- [Versioning](#versioning)
|
|
25
24
|
|
|
@@ -27,7 +26,7 @@ Stop clicking through Setup to grant access. Commit a YAML file, open a PR, let
|
|
|
27
26
|
|
|
28
27
|
## Why
|
|
29
28
|
|
|
30
|
-
Permission set assignments drift. People get access for a project and keep it forever. Offboarding misses a set. Nobody can answer "who can see X and why?" without a SOQL spelunking session.
|
|
29
|
+
Permission set assignments drift. People get access for a project and keep it forever. Offboarding misses a set. Nobody can answer "who can see X and why?" without a SOQL spelunking session. And in higher environments those grants happen by hand in Setup, with no review and no trail.
|
|
31
30
|
|
|
32
31
|
This plugin makes the desired state **declarative and reviewable**:
|
|
33
32
|
|
|
@@ -36,6 +35,8 @@ This plugin makes the desired state **declarative and reviewable**:
|
|
|
36
35
|
- ✅ **Safe by default:** deletions are opt-in and guarded by a delete threshold.
|
|
37
36
|
- ✅ **CI-native:** fully offline `check`, exit codes for gating, and `--json` on every command.
|
|
38
37
|
- ✅ **Flexible at the edges:** pick your file layout (by permission set or by user) and your sync mode.
|
|
38
|
+
- ✅ **GitOps for access, the SFDX way:** assignments live in source and ship through the same git and CI pipeline as your metadata, instead of being clicked into Setup by hand.
|
|
39
|
+
- ✅ **Fewer hands in Setup for higher environments:** because access is applied from git through CI, fewer people need direct Setup access in UAT and production, and every change is a reviewed pull request with a git audit trail.
|
|
39
40
|
|
|
40
41
|
## Install
|
|
41
42
|
|
|
@@ -195,29 +196,33 @@ Fully offline: runs in any CI job or pre-commit hook without org credentials.
|
|
|
195
196
|
|
|
196
197
|
```
|
|
197
198
|
USAGE
|
|
198
|
-
$ sf ps check -f <glob>... [--strict] [--json]
|
|
199
|
+
$ sf ps check -f <glob>... [--strict] [--watch] [--json]
|
|
199
200
|
|
|
200
201
|
FLAGS
|
|
201
202
|
-f, --file=<glob>... (required) YAML file(s) to read. Repeatable, globs are expanded by the plugin.
|
|
202
203
|
--strict Treat warnings as errors.
|
|
204
|
+
-w, --watch Re-run on every change to a matched file. Stays in the foreground until you stop it (Ctrl-C).
|
|
203
205
|
|
|
204
206
|
CHECKS
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
- valid YAML & schema (unknown keys rejected)
|
|
208
|
+
- duplicate assignees / duplicate (user, target) pairs
|
|
209
|
+
- conflicting intent across files
|
|
210
|
+
- empty or malformed assignee usernames
|
|
211
|
+
- internal referential integrity
|
|
210
212
|
```
|
|
211
213
|
|
|
214
|
+
`--watch` is for the local edit loop: leave it running while you tweak the YAML and the findings refresh on every save. It re-expands the globs each run, so files you add or delete are picked up too. It's a foreground, interactive mode, so it can't be combined with `--json` and isn't meant for CI, where a single gated `check` run is what you want.
|
|
215
|
+
|
|
212
216
|
### `sf ps validate`
|
|
213
217
|
|
|
214
218
|
```
|
|
215
219
|
USAGE
|
|
216
|
-
$ sf ps validate -o <org> -f <glob>... [--json]
|
|
220
|
+
$ sf ps validate -o <org> -f <glob>... [--watch] [--json]
|
|
217
221
|
|
|
218
222
|
FLAGS
|
|
219
223
|
-o, --target-org=<org> (required) Org to resolve against.
|
|
220
224
|
-f, --file=<glob>... (required) YAML file(s) to read. Repeatable, globs expanded by the plugin.
|
|
225
|
+
-w, --watch Re-run on every change to a matched file. Read-only, but each run queries the org, so point it at a scratch or dev org.
|
|
221
226
|
|
|
222
227
|
Runs all offline checks, then verifies that every user (active), permission set,
|
|
223
228
|
group, and license referenced actually exists and resolves uniquely.
|
|
@@ -227,13 +232,14 @@ group, and license referenced actually exists and resolves uniquely.
|
|
|
227
232
|
|
|
228
233
|
```
|
|
229
234
|
USAGE
|
|
230
|
-
$ sf ps plan -o <org> -f <glob>... [--mode <value>] [--fail-on-drift] [--json]
|
|
235
|
+
$ sf ps plan -o <org> -f <glob>... [--mode <value>] [--fail-on-drift] [--watch] [--json]
|
|
231
236
|
|
|
232
237
|
FLAGS
|
|
233
238
|
-o, --target-org=<org> (required)
|
|
234
239
|
-f, --file=<glob>... (required) YAML file(s) to read. Repeatable, globs expanded by the plugin.
|
|
235
240
|
--mode=<value> additive | destructive | sync [default: additive]
|
|
236
241
|
--fail-on-drift Exit non-zero if any change is pending (for CI gates).
|
|
242
|
+
-w, --watch Re-diff against the org on every change to a matched file. Read-only (never applies), but each run queries the org.
|
|
237
243
|
```
|
|
238
244
|
|
|
239
245
|
Example output:
|
|
@@ -291,73 +297,28 @@ FLAGS
|
|
|
291
297
|
--permission-sets=<names> Comma-separated list to export (default: all assignable).
|
|
292
298
|
```
|
|
293
299
|
|
|
294
|
-
##
|
|
300
|
+
## Inspiration & equivalents
|
|
295
301
|
|
|
296
|
-
|
|
302
|
+
This plugin's command surface borrows ideas from tools you already know:
|
|
297
303
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
pull_request:
|
|
303
|
-
push:
|
|
304
|
-
branches: [main]
|
|
305
|
-
|
|
306
|
-
jobs:
|
|
307
|
-
check:
|
|
308
|
-
runs-on: ubuntu-latest
|
|
309
|
-
steps:
|
|
310
|
-
- uses: actions/checkout@v4
|
|
311
|
-
- run: npm install -g @salesforce/cli
|
|
312
|
-
- run: sf plugins install sf-plugin-permission-sets
|
|
313
|
-
- run: sf ps check --file "permissions/*.yml" --strict
|
|
314
|
-
|
|
315
|
-
plan:
|
|
316
|
-
if: github.event_name == 'pull_request'
|
|
317
|
-
needs: check
|
|
318
|
-
runs-on: ubuntu-latest
|
|
319
|
-
steps:
|
|
320
|
-
- uses: actions/checkout@v4
|
|
321
|
-
- run: npm install -g @salesforce/cli
|
|
322
|
-
- run: sf plugins install sf-plugin-permission-sets
|
|
323
|
-
# Auth via Sfdx auth URL stored in a secrets manager, never hardcode credentials
|
|
324
|
-
- run: echo "$SF_AUTH_URL" | sf org login sfdx-url --sfdx-url-stdin --alias target
|
|
325
|
-
env:
|
|
326
|
-
SF_AUTH_URL: ${{ secrets.SF_AUTH_URL }}
|
|
327
|
-
- run: sf ps plan -o target --file "permissions/*.yml" --mode sync --fail-on-drift
|
|
328
|
-
|
|
329
|
-
apply:
|
|
330
|
-
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
331
|
-
needs: check
|
|
332
|
-
runs-on: ubuntu-latest
|
|
333
|
-
steps:
|
|
334
|
-
- uses: actions/checkout@v4
|
|
335
|
-
- run: npm install -g @salesforce/cli
|
|
336
|
-
- run: sf plugins install sf-plugin-permission-sets
|
|
337
|
-
- run: echo "$SF_AUTH_URL" | sf org login sfdx-url --sfdx-url-stdin --alias target
|
|
338
|
-
env:
|
|
339
|
-
SF_AUTH_URL: ${{ secrets.SF_AUTH_URL }}
|
|
340
|
-
- run: sf ps apply -o target --file "permissions/*.yml" --mode sync --no-prompt --max-deletes 25
|
|
341
|
-
```
|
|
304
|
+
- [Terraform](https://developer.hashicorp.com/terraform/docs)
|
|
305
|
+
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/)
|
|
306
|
+
- [AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/)
|
|
307
|
+
- [Salesforce CLI](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference.htm)
|
|
342
308
|
|
|
343
|
-
|
|
309
|
+
## Versioning
|
|
344
310
|
|
|
345
|
-
|
|
311
|
+
Releases follow [semantic versioning](https://semver.org). Snapshots are automatic, real releases are a manual decision.
|
|
346
312
|
|
|
347
|
-
|
|
313
|
+
**Automatic, no action needed:**
|
|
348
314
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
| `ps check` | `terraform validate` | `sam validate --lint` | n/a |
|
|
352
|
-
| `ps validate` | `terraform plan` (refresh) | `cfn validate-template` | `project deploy validate` |
|
|
353
|
-
| `ps plan` | `terraform plan` | `cfn create-change-set` | `project deploy preview` |
|
|
354
|
-
| `ps apply` | `terraform apply` | `cfn execute-change-set` / `sam deploy` | `project deploy start` |
|
|
355
|
-
| `ps export` | `terraform import` | n/a | n/a |
|
|
356
|
-
| `--fail-on-drift` | drift in plan exit code | `cfn detect-stack-drift` | n/a |
|
|
315
|
+
- Every push to `main` publishes a snapshot `0.0.0-dev.<run>` to the `dev` dist-tag.
|
|
316
|
+
- Creating a release triggers CI to build, stamp the version from the tag, publish it with provenance, and smoke-test the result.
|
|
357
317
|
|
|
358
|
-
|
|
318
|
+
**Manual, you decide and trigger:**
|
|
359
319
|
|
|
360
|
-
|
|
320
|
+
- Choosing the version bump (patch, minor, or major).
|
|
321
|
+
- Creating the GitHub Release, which is what triggers the publish above.
|
|
361
322
|
|
|
362
323
|
| Bump | When | Example tag |
|
|
363
324
|
| --- | --- | --- |
|
|
@@ -365,19 +326,19 @@ Releases follow [semantic versioning](https://semver.org). You choose the bump b
|
|
|
365
326
|
| minor | new backward-compatible feature | `v0.2.0` |
|
|
366
327
|
| major | breaking change to a command, flag, or the YAML schema | `v1.0.0` |
|
|
367
328
|
|
|
368
|
-
| dist-tag | Source | Install |
|
|
369
|
-
| --- | --- | --- |
|
|
370
|
-
| `latest` | a GitHub Release with a normal tag like `v1.2.0` | `sf plugins install sf-plugin-permission-sets` |
|
|
371
|
-
| `next` | a Release whose tag has a hyphen, like `v1.3.0-beta.1` | `sf plugins install sf-plugin-permission-sets@next` |
|
|
372
|
-
| `dev` | every push to `main`, published as `0.0.0-dev.<run>` | `sf plugins install sf-plugin-permission-sets@dev` |
|
|
373
|
-
|
|
374
329
|
Cut a release with a tag off `main`:
|
|
375
330
|
|
|
376
331
|
```bash
|
|
377
332
|
gh release create v0.2.0 --target main --title v0.2.0 --notes "Add ps export"
|
|
378
333
|
```
|
|
379
334
|
|
|
380
|
-
|
|
335
|
+
| dist-tag | Published by | Install |
|
|
336
|
+
| --- | --- | --- |
|
|
337
|
+
| `latest` | manual release with a normal tag like `v1.2.0` | `sf plugins install sf-plugin-permission-sets` |
|
|
338
|
+
| `next` | manual release with a hyphenated tag like `v1.3.0-beta.1` | `sf plugins install sf-plugin-permission-sets@next` |
|
|
339
|
+
| `dev` | automatic on every push to `main` | `sf plugins install sf-plugin-permission-sets@dev` |
|
|
340
|
+
|
|
341
|
+
The `next` tag is selected whenever the version contains a hyphen, not by GitHub's prerelease checkbox.
|
|
381
342
|
|
|
382
343
|
## License
|
|
383
344
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SfCommand } from '@salesforce/sf-plugins-core';
|
|
2
|
+
import { Finding } from '../../core/model.js';
|
|
3
|
+
export type PsCheckResult = {
|
|
4
|
+
files: number;
|
|
5
|
+
users: number;
|
|
6
|
+
assignments: number;
|
|
7
|
+
findings: Finding[];
|
|
8
|
+
};
|
|
9
|
+
export default class Check extends SfCommand<PsCheckResult> {
|
|
10
|
+
static readonly summary: string;
|
|
11
|
+
static readonly description: string;
|
|
12
|
+
static readonly examples: string[];
|
|
13
|
+
static readonly flags: {
|
|
14
|
+
file: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
strict: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<PsCheckResult>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
|
|
2
|
+
import { Messages } from '@salesforce/core';
|
|
3
|
+
import { CheckService } from '../../services/check.js';
|
|
4
|
+
import { formatFindings } from '../../core/report.js';
|
|
5
|
+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
6
|
+
const messages = Messages.loadMessages('sf-plugin-permission-sets', 'ps.check');
|
|
7
|
+
export default class Check extends SfCommand {
|
|
8
|
+
static summary = messages.getMessage('summary');
|
|
9
|
+
static description = messages.getMessage('description');
|
|
10
|
+
static examples = messages.getMessages('examples');
|
|
11
|
+
static flags = {
|
|
12
|
+
file: Flags.string({
|
|
13
|
+
char: 'f',
|
|
14
|
+
summary: messages.getMessage('flags.file.summary'),
|
|
15
|
+
required: true,
|
|
16
|
+
multiple: true,
|
|
17
|
+
}),
|
|
18
|
+
strict: Flags.boolean({
|
|
19
|
+
summary: messages.getMessage('flags.strict.summary'),
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
const { flags } = await this.parse(Check);
|
|
24
|
+
const result = await new CheckService().run({ files: flags.file, strict: flags.strict });
|
|
25
|
+
for (const line of formatFindings(result.findings)) {
|
|
26
|
+
this.log(line);
|
|
27
|
+
}
|
|
28
|
+
const users = new Set(result.assignments.map((a) => a.assignee)).size;
|
|
29
|
+
this.log('');
|
|
30
|
+
this.log(messages.getMessage('summary.counts', [String(result.errors), String(result.warnings)]));
|
|
31
|
+
if (result.failed) {
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
if (!this.jsonEnabled()) {
|
|
34
|
+
this.error(messages.getMessage('error.failed'), { exit: 1 });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
files: result.files.length,
|
|
39
|
+
users,
|
|
40
|
+
assignments: result.assignments.length,
|
|
41
|
+
findings: result.findings,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/commands/ps/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,QAAQ,CAAC,kCAAkC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC7D,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC,2BAA2B,EAAE,UAAU,CAAC,CAAC;AAShF,MAAM,CAAC,OAAO,OAAO,KAAM,SAAQ,SAAwB;IAClD,MAAM,CAAU,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACzD,MAAM,CAAU,WAAW,GAAG,QAAQ,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IACjE,MAAM,CAAU,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;IAE5D,MAAM,CAAU,KAAK,GAAG;QAC7B,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC;YACjB,IAAI,EAAE,GAAG;YACT,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,oBAAoB,CAAC;YAClD,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC;YACpB,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,sBAAsB,CAAC;SACrD,CAAC;KACH,CAAC;IAEK,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,YAAY,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAEzF,KAAK,MAAM,IAAI,IAAI,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;QACtE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAElG,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC;QAED,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;YAC1B,KAAK;YACL,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM;YACtC,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAC;IACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DesiredAssignment, Finding, LoadResult } from './model.js';
|
|
2
|
+
/** Process one file's text through parse, validate, and normalize. Pure, no disk. */
|
|
3
|
+
export declare function checkContent(text: string, file: string): {
|
|
4
|
+
assignments: DesiredAssignment[];
|
|
5
|
+
findings: Finding[];
|
|
6
|
+
};
|
|
7
|
+
/** Expand the globs, read every matched file, and merge into one model by union. */
|
|
8
|
+
export declare function loadFiles(patterns: string[]): Promise<LoadResult>;
|
package/lib/core/load.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { globby } from 'globby';
|
|
3
|
+
import { parseFile } from './parse.js';
|
|
4
|
+
import { validateFile } from './schema.js';
|
|
5
|
+
import { normalize } from './normalize.js';
|
|
6
|
+
/** Process one file's text through parse, validate, and normalize. Pure, no disk. */
|
|
7
|
+
export function checkContent(text, file) {
|
|
8
|
+
const parsed = parseFile(text, file);
|
|
9
|
+
if (!parsed.data) {
|
|
10
|
+
return { assignments: [], findings: parsed.findings };
|
|
11
|
+
}
|
|
12
|
+
const validated = validateFile(parsed.data, file);
|
|
13
|
+
if (!validated.data) {
|
|
14
|
+
return { assignments: [], findings: [...parsed.findings, ...validated.findings] };
|
|
15
|
+
}
|
|
16
|
+
const normalized = normalize(validated.data, file);
|
|
17
|
+
return {
|
|
18
|
+
assignments: normalized.assignments,
|
|
19
|
+
findings: [...parsed.findings, ...validated.findings, ...normalized.findings],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Expand the globs, read every matched file, and merge into one model by union. */
|
|
23
|
+
export async function loadFiles(patterns) {
|
|
24
|
+
const files = await globby(patterns);
|
|
25
|
+
if (files.length === 0) {
|
|
26
|
+
return {
|
|
27
|
+
files,
|
|
28
|
+
assignments: [],
|
|
29
|
+
findings: [{ level: 'error', code: 'NO_FILES', message: `no files matched: ${patterns.join(', ')}` }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const findings = [];
|
|
33
|
+
const collected = [];
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
// eslint-disable-next-line no-await-in-loop
|
|
36
|
+
const text = await readFile(file, 'utf8');
|
|
37
|
+
const res = checkContent(text, file);
|
|
38
|
+
findings.push(...res.findings);
|
|
39
|
+
collected.push(...res.assignments);
|
|
40
|
+
}
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
const assignments = [];
|
|
43
|
+
for (const a of collected) {
|
|
44
|
+
const dedupeKey = `${a.assignee} ${a.kind} ${a.target}`;
|
|
45
|
+
if (seen.has(dedupeKey))
|
|
46
|
+
continue;
|
|
47
|
+
seen.add(dedupeKey);
|
|
48
|
+
assignments.push(a);
|
|
49
|
+
}
|
|
50
|
+
return { files, assignments, findings };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=load.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/core/load.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAY;IACrD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;IACxD,CAAC;IAED,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClD,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QACpB,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,GAAG,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;IACpF,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnD,OAAO;QACL,WAAW,EAAE,UAAU,CAAC,WAAW;QACnC,QAAQ,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,GAAG,SAAS,CAAC,QAAQ,EAAE,GAAG,UAAU,CAAC,QAAQ,CAAC;KAC9E,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAkB;IAChD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,KAAK;YACL,WAAW,EAAE,EAAE;YACf,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,qBAAqB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;SACtG,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAwB,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,4CAA4C;QAC5C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACrC,QAAQ,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC/B,SAAS,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,WAAW,GAAwB,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;QACxD,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,SAAS;QAClC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpB,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type Kind = 'permissionSet' | 'permissionSetGroup' | 'permissionSetLicense';
|
|
2
|
+
export type DesiredAssignment = {
|
|
3
|
+
assignee: string;
|
|
4
|
+
kind: Kind;
|
|
5
|
+
target: string;
|
|
6
|
+
};
|
|
7
|
+
export type FindingLevel = 'error' | 'warning';
|
|
8
|
+
export type Finding = {
|
|
9
|
+
level: FindingLevel;
|
|
10
|
+
code: string;
|
|
11
|
+
message: string;
|
|
12
|
+
file?: string;
|
|
13
|
+
line?: number;
|
|
14
|
+
};
|
|
15
|
+
export type LoadResult = {
|
|
16
|
+
files: string[];
|
|
17
|
+
assignments: DesiredAssignment[];
|
|
18
|
+
findings: Finding[];
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/core/model.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FileShape } from './schema.js';
|
|
2
|
+
import { DesiredAssignment, Finding } from './model.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turn a validated file into canonical (assignee, kind, target) tuples, and
|
|
5
|
+
* emit the structural findings: duplicate targets, empty lists, empty users.
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalize(data: FileShape, file: string): {
|
|
8
|
+
assignments: DesiredAssignment[];
|
|
9
|
+
findings: Finding[];
|
|
10
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const KIND_KEYS = [
|
|
2
|
+
['permissionSet', 'permissionSets'],
|
|
3
|
+
['permissionSetGroup', 'permissionSetGroups'],
|
|
4
|
+
['permissionSetLicense', 'permissionSetLicenses'],
|
|
5
|
+
];
|
|
6
|
+
/**
|
|
7
|
+
* Turn a validated file into canonical (assignee, kind, target) tuples, and
|
|
8
|
+
* emit the structural findings: duplicate targets, empty lists, empty users.
|
|
9
|
+
*/
|
|
10
|
+
export function normalize(data, file) {
|
|
11
|
+
const assignments = [];
|
|
12
|
+
const findings = [];
|
|
13
|
+
for (const [username, entry] of Object.entries(data.users)) {
|
|
14
|
+
let scopeCount = 0;
|
|
15
|
+
for (const [kind, key] of KIND_KEYS) {
|
|
16
|
+
const list = entry[key];
|
|
17
|
+
if (list === undefined)
|
|
18
|
+
continue;
|
|
19
|
+
if (list.length === 0) {
|
|
20
|
+
findings.push({ level: 'warning', code: 'EMPTY_LIST', message: `${username}: ${key} is empty`, file });
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
scopeCount += 1;
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
for (const target of list) {
|
|
26
|
+
if (seen.has(target)) {
|
|
27
|
+
findings.push({
|
|
28
|
+
level: 'warning',
|
|
29
|
+
code: 'DUP_TARGET',
|
|
30
|
+
message: `${username}: ${target} is listed twice under ${key}`,
|
|
31
|
+
file,
|
|
32
|
+
});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
seen.add(target);
|
|
36
|
+
assignments.push({ assignee: username, kind, target });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (scopeCount === 0) {
|
|
40
|
+
findings.push({ level: 'warning', code: 'EMPTY_USER', message: `${username}: no scopes declared`, file });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { assignments, findings };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/core/normalize.ts"],"names":[],"mappings":"AAKA,MAAM,SAAS,GAA4B;IACzC,CAAC,eAAe,EAAE,gBAAgB,CAAC;IACnC,CAAC,oBAAoB,EAAE,qBAAqB,CAAC;IAC7C,CAAC,sBAAsB,EAAE,uBAAuB,CAAC;CAClD,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAe,EAAE,IAAY;IACrD,MAAM,WAAW,GAAwB,EAAE,CAAC;IAC5C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3D,IAAI,UAAU,GAAG,CAAC,CAAC;QAEnB,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,SAAS,EAAE,CAAC;YACpC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;YACxB,IAAI,IAAI,KAAK,SAAS;gBAAE,SAAS;YACjC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,QAAQ,KAAK,GAAG,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;gBACvG,SAAS;YACX,CAAC;YAED,UAAU,IAAI,CAAC,CAAC;YAChB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;YAC/B,KAAK,MAAM,MAAM,IAAI,IAAI,EAAE,CAAC;gBAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;oBACrB,QAAQ,CAAC,IAAI,CAAC;wBACZ,KAAK,EAAE,SAAS;wBAChB,IAAI,EAAE,YAAY;wBAClB,OAAO,EAAE,GAAG,QAAQ,KAAK,MAAM,0BAA0B,GAAG,EAAE;wBAC9D,IAAI;qBACL,CAAC,CAAC;oBACH,SAAS;gBACX,CAAC;gBACD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACjB,WAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;QAED,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,GAAG,QAAQ,sBAAsB,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5G,CAAC;IACH,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Finding } from './model.js';
|
|
2
|
+
/**
|
|
3
|
+
* Read one file's text into a plain object. Reports invalid YAML and duplicate
|
|
4
|
+
* keys (uniqueKeys), and treats an empty document as a warning.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseFile(text: string, file: string): {
|
|
7
|
+
data?: unknown;
|
|
8
|
+
findings: Finding[];
|
|
9
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { parseDocument } from 'yaml';
|
|
2
|
+
/**
|
|
3
|
+
* Read one file's text into a plain object. Reports invalid YAML and duplicate
|
|
4
|
+
* keys (uniqueKeys), and treats an empty document as a warning.
|
|
5
|
+
*/
|
|
6
|
+
export function parseFile(text, file) {
|
|
7
|
+
const doc = parseDocument(text, { uniqueKeys: true });
|
|
8
|
+
if (doc.errors.length > 0) {
|
|
9
|
+
return {
|
|
10
|
+
findings: doc.errors.map((err) => ({
|
|
11
|
+
level: 'error',
|
|
12
|
+
code: 'YAML',
|
|
13
|
+
message: err.message,
|
|
14
|
+
file,
|
|
15
|
+
line: err.linePos?.[0]?.line,
|
|
16
|
+
})),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const data = doc.toJS();
|
|
20
|
+
if (data === null || data === undefined) {
|
|
21
|
+
return { findings: [{ level: 'warning', code: 'EMPTY_FILE', message: 'file is empty', file }] };
|
|
22
|
+
}
|
|
23
|
+
return { data, findings: [] };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/core/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAGrC;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,IAAY;IAClD,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtD,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACjC,KAAK,EAAE,OAAO;gBACd,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,IAAI;gBACJ,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI;aAC7B,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAa,CAAC;IACnC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACxC,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAClG,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Finding } from './model.js';
|
|
2
|
+
/** Render findings as human-readable lines. Shared by check, validate, and plan. */
|
|
3
|
+
export declare function formatFindings(findings: Finding[]): string[];
|
|
4
|
+
/** Count findings by level. */
|
|
5
|
+
export declare function countFindings(findings: Finding[]): {
|
|
6
|
+
errors: number;
|
|
7
|
+
warnings: number;
|
|
8
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Render findings as human-readable lines. Shared by check, validate, and plan. */
|
|
2
|
+
export function formatFindings(findings) {
|
|
3
|
+
return findings.map((finding) => {
|
|
4
|
+
const where = finding.file ? `${finding.file}${finding.line ? `:${finding.line}` : ''} ` : '';
|
|
5
|
+
return `${finding.level}: ${where}${finding.message}`;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
/** Count findings by level. */
|
|
9
|
+
export function countFindings(findings) {
|
|
10
|
+
return {
|
|
11
|
+
errors: findings.filter((f) => f.level === 'error').length,
|
|
12
|
+
warnings: findings.filter((f) => f.level === 'warning').length,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/core/report.ts"],"names":[],"mappings":"AAEA,oFAAoF;AACpF,MAAM,UAAU,cAAc,CAAC,QAAmB;IAChD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9F,OAAO,GAAG,OAAO,CAAC,KAAK,KAAK,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+BAA+B;AAC/B,MAAM,UAAU,aAAa,CAAC,QAAmB;IAC/C,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,MAAM;QAC1D,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,MAAM;KAC/D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Finding } from './model.js';
|
|
3
|
+
export declare const userEntrySchema: z.ZodObject<{
|
|
4
|
+
permissionSets: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
5
|
+
permissionSetGroups: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
6
|
+
permissionSetLicenses: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
7
|
+
}, z.core.$strict>;
|
|
8
|
+
export declare const fileSchema: z.ZodObject<{
|
|
9
|
+
users: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
10
|
+
permissionSets: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11
|
+
permissionSetGroups: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
|
+
permissionSetLicenses: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
13
|
+
}, z.core.$strict>>;
|
|
14
|
+
}, z.core.$strict>;
|
|
15
|
+
export type FileShape = z.infer<typeof fileSchema>;
|
|
16
|
+
/** Validate a parsed object against the file contract, turning issues into findings. */
|
|
17
|
+
export declare function validateFile(data: unknown, file: string): {
|
|
18
|
+
data?: FileShape;
|
|
19
|
+
findings: Finding[];
|
|
20
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const scopeList = z.array(z.string().min(1)).optional();
|
|
3
|
+
export const userEntrySchema = z.strictObject({
|
|
4
|
+
permissionSets: scopeList,
|
|
5
|
+
permissionSetGroups: scopeList,
|
|
6
|
+
permissionSetLicenses: scopeList,
|
|
7
|
+
});
|
|
8
|
+
export const fileSchema = z.strictObject({
|
|
9
|
+
users: z.record(z.string().min(1), userEntrySchema),
|
|
10
|
+
});
|
|
11
|
+
/** Validate a parsed object against the file contract, turning issues into findings. */
|
|
12
|
+
export function validateFile(data, file) {
|
|
13
|
+
const parsed = fileSchema.safeParse(data);
|
|
14
|
+
if (parsed.success) {
|
|
15
|
+
return { data: parsed.data, findings: [] };
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
findings: parsed.error.issues.map((issue) => ({
|
|
19
|
+
level: 'error',
|
|
20
|
+
code: 'SCHEMA',
|
|
21
|
+
message: `${issue.path.join('.') || '(root)'}: ${issue.message}`,
|
|
22
|
+
file,
|
|
23
|
+
})),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# 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;AAGxB,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AAExD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,YAAY,CAAC;IAC5C,cAAc,EAAE,SAAS;IACzB,mBAAmB,EAAE,SAAS;IAC9B,qBAAqB,EAAE,SAAS;CACjC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC;IACvC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC;CACpD,CAAC,CAAC;AAIH,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAAC,IAAa,EAAE,IAAY;IACtD,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC7C,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC5C,KAAK,EAAE,OAAO;YACd,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE;YAChE,IAAI;SACL,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DesiredAssignment, Finding } from '../core/model.js';
|
|
2
|
+
export type CheckOptions = {
|
|
3
|
+
files: string[];
|
|
4
|
+
strict: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type CheckResult = {
|
|
7
|
+
files: string[];
|
|
8
|
+
assignments: DesiredAssignment[];
|
|
9
|
+
findings: Finding[];
|
|
10
|
+
errors: number;
|
|
11
|
+
warnings: number;
|
|
12
|
+
failed: boolean;
|
|
13
|
+
};
|
|
14
|
+
/** Offline check: load the files, validate them, and summarize the findings. */
|
|
15
|
+
export declare class CheckService {
|
|
16
|
+
run(options: CheckOptions): Promise<CheckResult>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { loadFiles } from '../core/load.js';
|
|
2
|
+
import { countFindings } from '../core/report.js';
|
|
3
|
+
/** Offline check: load the files, validate them, and summarize the findings. */
|
|
4
|
+
export class CheckService {
|
|
5
|
+
// eslint-disable-next-line class-methods-use-this
|
|
6
|
+
async run(options) {
|
|
7
|
+
const loaded = await loadFiles(options.files);
|
|
8
|
+
const { errors, warnings } = countFindings(loaded.findings);
|
|
9
|
+
const failed = errors > 0 || (options.strict && warnings > 0);
|
|
10
|
+
return {
|
|
11
|
+
files: loaded.files,
|
|
12
|
+
assignments: loaded.assignments,
|
|
13
|
+
findings: loaded.findings,
|
|
14
|
+
errors,
|
|
15
|
+
warnings,
|
|
16
|
+
failed,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# 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;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAiBlD,gFAAgF;AAChF,MAAM,OAAO,YAAY;IACvB,kDAAkD;IAC3C,KAAK,CAAC,GAAG,CAAC,OAAqB;QACpC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;QAE9D,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM;YACN,QAAQ;YACR,MAAM;SACP,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -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
|