plumb-fhir 0.6.2 → 0.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 (2026-10-05)
4
+
5
+ - **`plumb check`** reports every `MedplumClient` read or write of a type a
6
+ selected profile fully holds that goes around `readProfiled`,
7
+ `searchProfiled`, `createProfiled`, `updateProfiled` or `stampProfiled`,
8
+ by the types the compiler infers, so an inferred write is found as surely as
9
+ a literal one. A committed baseline lets a project adopt it with a backlog:
10
+ only new or grown findings fail. `// plumb-check: <reason>` marks a
11
+ deliberate exception. It needs the project's TypeScript 5 or 6 (design 07).
12
+ - **`stampProfiled` returns its resource branded** (`T & Stamped`, an optional
13
+ unique-symbol property), which `check` reads to accept a stamped write
14
+ through a variable. Callers see no difference.
15
+ - `check` in the config: `tsconfig`, `baseline` and `ignore`.
16
+
17
+ ## 0.7.0 (2026-10-05)
18
+
19
+ - **`stampProfiled(resource, options?)`** returns the copy `createProfiled`
20
+ would write, routed and stamped, without writing it: for conditional
21
+ creates, upserts and batch or transaction entries. It takes the same
22
+ options, is typed the same way, and throws the same `RoutingError` (#124).
23
+ - The README says `updateProfiled` stamps a record that was unstamped, so
24
+ edits of stored records that fail the profile start being refused; and to
25
+ commit `fsh-generated/resources/` but gitignore SUSHI's index files (#125).
26
+
3
27
  ## 0.6.2 (2026-10-05)
4
28
 
5
29
  `validate` and `push` from an installed Plumb, and on Medplum's `awslambda`
package/README.md CHANGED
@@ -185,8 +185,11 @@ export default defineConfig({
185
185
  ✔ sushi 1 StructureDefinitions, 0 ValueSets 6.8s
186
186
  ```
187
187
 
188
- - **Commit `fsh-generated/`** with the FSH. It is the JSON `push` loads into
189
- Medplum, and `validateProfiled` reads it without running SUSHI.
188
+ - **Commit `fsh-generated/resources/`** with the FSH. It is the JSON `push`
189
+ loads into Medplum, and `validateProfiled` reads it without running SUSHI.
190
+ SUSHI's own `fsh-index.txt` and `data/` are bookkeeping no command reads:
191
+ gitignore them. Marking the generated folders `linguist-generated` in
192
+ `.gitattributes` collapses them in review diffs.
190
193
  - **`generate --check` in CI** rebuilds the FSH into a temporary folder and
191
194
  fails when the committed `fsh-generated/` or the types differ from it.
192
195
  - **SUSHI's errors stop `generate`**, with their FSH file and line. Its
@@ -240,6 +243,22 @@ const saved = await createProfiled(medplum, bp); // stamped, then medplum.create
240
243
  - They take any client with `createResource` and `updateResource`, such as
241
244
  a `MedplumClient`, so the generated code needs nothing from Plumb at run
242
245
  time.
246
+ - **`stampProfiled(resource, options?)`** returns the copy `createProfiled`
247
+ would write, stamped and not written, for the writes it cannot make: a
248
+ conditional create, an upsert, a batch or transaction entry. It takes the
249
+ same options and throws the same `RoutingError`:
250
+
251
+ ```ts
252
+ await medplum.createResourceIfNoneExist(
253
+ stampProfiled(coverage, { profile: USCoreCoverageProfileUrl }),
254
+ `identifier=${system}|${value}`,
255
+ );
256
+ ```
257
+
258
+ - **Moving an edit path to `updateProfiled` stamps records that were
259
+ unstamped**, so the server starts holding them to the profile: an edit of a
260
+ stored record that does not meet it is refused from then on. Run
261
+ `plumb validate` and fix what fails before moving edit paths.
243
262
 
244
263
  **Keys come from each profile:** every fixed or pattern value on a required
245
264
  first-level element (a pinned `code`), and every required slice's
@@ -360,6 +379,61 @@ read would refuse. History is not offered typed: an old version may predate
360
379
  its stamp, so read it with `medplum.readHistory` and `asProfiled` it if you
361
380
  must.
362
381
 
382
+ ## Find code that goes around Plumb
383
+
384
+ The profiled reads and writes only help where they are called. `plumb check`
385
+ reads your code the way the compiler does and reports every `MedplumClient`
386
+ read or write of a profiled type that does not go through them, including a
387
+ write whose type is only inferred:
388
+
389
+ ```ts
390
+ export default defineConfig({
391
+ // …igs, profiles, out
392
+ check: {
393
+ tsconfig: ['apps/web/tsconfig.json'],
394
+ baseline: './plumb-check-baseline.json',
395
+ ignore: ['**/*.test.ts', '**/*.stories.tsx'],
396
+ },
397
+ });
398
+ ```
399
+
400
+ ```bash
401
+ npx plumb check
402
+ ```
403
+
404
+ ```text
405
+ plumb check
406
+ ✖ check 2 new, 248 in the baseline, in 2767 files 6.2s
407
+ apps/web/src/lib/goals/goal.ts:41:10 readResource Goal → readProfiled
408
+ apps/web/src/lib/coverage/save.ts:90:5 createResource Coverage → createProfiled, updateProfiled or stampProfiled
409
+ Failed in 6.2s
410
+ ```
411
+
412
+ - **What it reports:** `readResource`, `searchResources`, `searchOne`,
413
+ `searchResourcePages`, `createResource`, `updateResource`, `upsertResource`
414
+ and `createResourceIfNoneExist` on a `MedplumClient` (or a class deriving
415
+ from it), for a type every resource of which a selected profile holds: one
416
+ with a profile that routes on no keys, or a `defaultProfile`. A type whose
417
+ profiles are keyed on content (one code of Observation) is not reported, and
418
+ neither is `string` or `ResourceType`.
419
+ - **A stamped write passes,** through a variable too: `stampProfiled` returns
420
+ its resource branded, and `check` reads the brand from the type.
421
+ - **A deliberate exception** takes a comment with a reason on the line before:
422
+ `// plumb-check: the backfill reads unstamped records to stamp them`.
423
+ - **The baseline** is a committed count per file, method and type. `check`
424
+ fails only when a count grows or a new one appears, so new raw access is
425
+ refused at once while the backlog shrinks. `--update-baseline` records
426
+ fixes; it refuses growth unless `--allow-growth` is given.
427
+ - **Paths** in the output, the baseline and `ignore` are relative to the
428
+ deepest folder holding the config and every tsconfig, so a monorepo's config
429
+ in one package checks code in another.
430
+ - **TypeScript:** `check` compiles with your project's own `typescript`, which
431
+ needs its compiler API: TypeScript 5 or 6. TypeScript 7's native package has
432
+ none yet, and `check` exits 2 on it.
433
+
434
+ Exit codes: 0 when nothing is new, 1 when something is (or the baseline would
435
+ grow), 2 for usage, config and set-up errors.
436
+
363
437
  ## Check stored data, then load profiles
364
438
 
365
439
  Medplum validates a resource when it is written, and never again. Tightening a
@@ -0,0 +1,89 @@
1
+ import type * as TS from 'typescript5';
2
+ import type { PlumbConfig } from './config.js';
3
+ import { type Route } from './emit/routes.js';
4
+ /** A `MedplumClient` read or write of a profiled type that goes around Plumb. */
5
+ export interface Finding {
6
+ /** Relative to the config's folder, with forward slashes. */
7
+ file: string;
8
+ line: number;
9
+ column: number;
10
+ method: string;
11
+ /** The profiled resource types the call can touch, sorted. */
12
+ resourceTypes: string[];
13
+ /** What to call instead, or why the finding stands. */
14
+ instead: string;
15
+ }
16
+ export interface FindOptions {
17
+ /** The project's TypeScript module, with its compiler API. */
18
+ ts: typeof TS;
19
+ /** The folder findings are relative to, and `ignore` is matched from. */
20
+ base: string;
21
+ tsconfig: string[];
22
+ /** Resource types a selected profile constrains. */
23
+ profiledTypes: ReadonlySet<string>;
24
+ /** The generated folder, never checked. */
25
+ out: string;
26
+ ignore?: string[];
27
+ }
28
+ /**
29
+ * Compiles each project and reports every raw `MedplumClient` read or write of
30
+ * a profiled type, with the type the checker infers, not the text written.
31
+ */
32
+ export declare function findRawAccess(options: FindOptions): {
33
+ findings: Finding[];
34
+ files: number;
35
+ };
36
+ /** Accepted findings, counted per file, method and resource types. */
37
+ export type Baseline = Record<string, number>;
38
+ export declare function countFindings(findings: Finding[]): Baseline;
39
+ export interface BaselineComparison {
40
+ /** Findings under a key that is new or grew: every one of them, since a line cannot be told apart. */
41
+ fresh: Finding[];
42
+ /** How many findings the baseline accepts that are now gone. */
43
+ fixed: number;
44
+ /** Findings the baseline accepts. */
45
+ accepted: number;
46
+ }
47
+ /** A finding fails only where its key's count exceeds the baseline's. */
48
+ export declare function compareBaseline(findings: Finding[], baseline: Baseline): BaselineComparison;
49
+ export interface CheckResult {
50
+ ok: boolean;
51
+ findings: Finding[];
52
+ comparison: BaselineComparison;
53
+ files: number;
54
+ /** Set when `updateBaseline` wrote the file. */
55
+ baselineWritten?: boolean;
56
+ errors: {
57
+ code: 'no-check-config' | 'no-compiler-api' | 'no-lock' | 'baseline-growth';
58
+ message: string;
59
+ }[];
60
+ }
61
+ export interface CheckOptions {
62
+ config: PlumbConfig;
63
+ configPath: string;
64
+ /** The project's TypeScript; resolved from the config's folder by default. */
65
+ ts?: typeof TS;
66
+ cacheDir?: string;
67
+ updateBaseline?: boolean;
68
+ allowGrowth?: boolean;
69
+ }
70
+ /**
71
+ * `plumb check`: loads the selected profiles for the types they constrain,
72
+ * compiles the configured projects, and compares what it finds with the
73
+ * baseline, or rewrites the baseline.
74
+ */
75
+ export declare function checkProject(options: CheckOptions): Promise<CheckResult>;
76
+ /**
77
+ * The folder findings and `ignore` globs are relative to: the deepest one
78
+ * holding the config and every tsconfig, so a monorepo's config in one package
79
+ * checks code in another, and baseline keys do not depend on where it runs.
80
+ */
81
+ export declare function checkRoot(configPath: string, tsconfig: string[]): string;
82
+ /**
83
+ * The types every resource of which a selected profile holds: one with a
84
+ * profile that routes on no keys, or a `defaultProfile`. A profile keyed on
85
+ * content (one code of Observation) holds only some, so raw access to the type
86
+ * is not wrong and is not reported.
87
+ */
88
+ export declare function enforcedTypes(routes: Record<string, Route[]>, defaultProfile?: Record<string, string[]>): Set<string>;
89
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/check.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,KAAK,KAAK,EAAe,MAAM,kBAAkB,CAAC;AAI3D,iFAAiF;AACjF,MAAM,WAAW,OAAO;IACtB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;CACjB;AAiBD,MAAM,WAAW,WAAW;IAC1B,8DAA8D;IAC9D,EAAE,EAAE,OAAO,EAAE,CAAC;IACd,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oDAAoD;IACpD,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAe1F;AAoHD,sEAAsE;AACtE,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAI9C,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,QAAQ,CAI3D;AAED,MAAM,WAAW,kBAAkB;IACjC,sGAAsG;IACtG,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,kBAAkB,CAS3F;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE;QACN,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,SAAS,GAAG,iBAAiB,CAAC;QAC5E,OAAO,EAAE,MAAM,CAAC;KACjB,EAAE,CAAC;CACL;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA2D9E;AAeD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAQxE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,EAC/B,cAAc,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAM,GAC5C,GAAG,CAAC,MAAM,CAAC,CAKb"}
@@ -1,3 +1,4 @@
1
+ import type * as TS from 'typescript5';
1
2
  import { type Checked } from './conformance.js';
2
3
  import { type Step } from './generate.js';
3
4
  export interface CliIo {
@@ -9,6 +10,8 @@ export interface CliIo {
9
10
  stderr: (text: string) => void;
10
11
  cacheDir?: string;
11
12
  fetch?: typeof globalThis.fetch;
13
+ /** The TypeScript `check` compiles with; the project's own by default. */
14
+ typescript?: typeof TS;
12
15
  }
13
16
  /** What a step did, as one line: its name, its counts and its time. */
14
17
  export declare function formatStep(step: Step, out: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,OAAO,EAIb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAiC,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAGzE,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,6DAA6D;IAC7D,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAyDD,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAe1D;AAED,2EAA2E;AAC3E,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBpE;AAqID,KAAK,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC;AAIjD,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,EAAE,CAexD"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAGvC,OAAO,EACL,KAAK,OAAO,EAIb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAiC,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAGzE,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,6DAA6D;IAC7D,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,0EAA0E;IAC1E,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC;CACxB;AA+DD,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAe1D;AAED,2EAA2E;AAC3E,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBpE;AA8KD,KAAK,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC;AAIjD,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,EAAE,CAexD"}
@@ -36,6 +36,17 @@ export interface PlumbConfig {
36
36
  defaultProfile?: Record<string, string[]>;
37
37
  /** What `push` writes to each environment's project besides its profiles. */
38
38
  project?: ProjectConfig;
39
+ /** What `plumb check` reads. */
40
+ check?: CheckConfig;
41
+ }
42
+ /** What `plumb check` reads: the project's code, for raw access to profiled types. */
43
+ export interface CheckConfig {
44
+ /** The TypeScript projects to check, each compiled once; a path or several. */
45
+ tsconfig: string | string[];
46
+ /** The committed file of accepted findings; without one, every finding fails. */
47
+ baseline?: string;
48
+ /** Globs, relative to the config, of files not to check (tests, stories). */
49
+ ignore?: string[];
39
50
  }
40
51
  /**
41
52
  * A project's configuration, as a project admin can write it. Policies and
@@ -94,7 +105,7 @@ export interface ResolvedEnvironment {
94
105
  clientId: string;
95
106
  clientSecret: string;
96
107
  }
97
- export type ConfigErrorCode = 'config-not-found' | 'unsupported-syntax' | 'unresolved-import' | 'no-default-export' | 'unknown-key' | 'missing-out' | 'invalid-type' | 'invalid-ig' | 'unlisted-ig' | 'invalid-profile' | 'invalid-max-codes' | 'invalid-base-url' | 'unknown-environment' | 'missing-variable' | 'invalid-route' | 'unselected-route' | 'invalid-route-element' | 'invalid-default-profile' | 'versioned-url' | 'fsh-and-local' | 'no-sushi-config' | 'invalid-setting' | 'super-admin-field' | 'unknown-access-policy' | 'duplicate-key';
108
+ export type ConfigErrorCode = 'config-not-found' | 'unsupported-syntax' | 'unresolved-import' | 'no-default-export' | 'unknown-key' | 'missing-out' | 'invalid-type' | 'invalid-ig' | 'unlisted-ig' | 'invalid-profile' | 'invalid-max-codes' | 'invalid-base-url' | 'unknown-environment' | 'missing-variable' | 'invalid-route' | 'unselected-route' | 'invalid-route-element' | 'invalid-default-profile' | 'versioned-url' | 'fsh-and-local' | 'no-sushi-config' | 'invalid-setting' | 'super-admin-field' | 'unknown-access-policy' | 'duplicate-key' | 'invalid-check';
98
109
  export interface ConfigError {
99
110
  code: ConfigErrorCode;
100
111
  message: string;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"print.d.ts","sourceRoot":"","sources":["../../../src/emit/print.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAS,OAAO,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAiB,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE5E,uFAAuF;AACvF,eAAO,MAAM,MAAM,0BAA0B,CAAC;AAqG9C,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,CAAC,EAAE,QAAQ,GAAG,MAAM,CAgD7C;AA8UD,KAAK,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC,CAAC;AAkR5F,kHAAkH;AAClH,wBAAgB,UAAU,CACxB,MAAM,EAAE,YAAY,EAAE,EACtB,MAAM,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,EACvC,OAAO,GAAE,YAAiF,GACzF,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAyBrB"}
1
+ {"version":3,"file":"print.d.ts","sourceRoot":"","sources":["../../../src/emit/print.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAS,OAAO,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAiB,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE5E,uFAAuF;AACvF,eAAO,MAAM,MAAM,0BAA0B,CAAC;AAqG9C,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,CAAC,EAAE,QAAQ,GAAG,MAAM,CAgD7C;AA2WD,KAAK,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC,CAAC;AAkR5F,kHAAkH;AAClH,wBAAgB,UAAU,CACxB,MAAM,EAAE,YAAY,EAAE,EACtB,MAAM,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,EACvC,OAAO,GAAE,YAAiF,GACzF,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAyBrB"}
@@ -44,7 +44,8 @@ var KEYS = /* @__PURE__ */ new Set([
44
44
  "environments",
45
45
  "routes",
46
46
  "defaultProfile",
47
- "project"
47
+ "project",
48
+ "check"
48
49
  ]);
49
50
  var ENVIRONMENT_KEYS = ["baseUrl", "clientId", "clientSecret", "settings"];
50
51
  var PROJECT_KEYS = ["settings", "secrets", "accessPolicies", "defaultAccessPolicies", "clients"];
@@ -92,6 +93,7 @@ async function loadConfig(options) {
92
93
  config: {
93
94
  ...config,
94
95
  out: (0, import_node_path.resolve)(base2, config.out),
96
+ ...config.check ? { check: resolveCheck(base2, config.check) } : {},
95
97
  ...fsh ? { fsh } : {},
96
98
  ...local ? { local } : {}
97
99
  }
@@ -139,7 +141,8 @@ function check(config) {
139
141
  ...checkEnvironments(record.environments),
140
142
  ...checkRouteRows(record),
141
143
  ...checkDefaultProfile(record.defaultProfile),
142
- ...checkProject(record.project)
144
+ ...checkProject(record.project),
145
+ ...checkCheck(record.check)
143
146
  );
144
147
  for (const key of ["local", "fsh"]) {
145
148
  if (record[key] !== void 0 && typeof record[key] !== "string") {
@@ -184,6 +187,32 @@ function checkWildcards(record) {
184
187
  ] : [];
185
188
  });
186
189
  }
190
+ function checkCheck(check2) {
191
+ if (check2 === void 0) return [];
192
+ if (typeof check2 !== "object" || check2 === null || Array.isArray(check2)) {
193
+ return [{ code: "invalid-type", path: "check", message: '"check" must be an object.' }];
194
+ }
195
+ const errors = Object.keys(check2).filter((key) => !["tsconfig", "baseline", "ignore"].includes(key)).map((key) => ({
196
+ code: "unknown-key",
197
+ path: `check.${key}`,
198
+ message: `Unknown config key "check.${key}".`
199
+ }));
200
+ const { tsconfig, baseline, ignore } = check2;
201
+ const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
202
+ const invalid = (key, expected) => ({
203
+ code: "invalid-check",
204
+ path: `check.${key}`,
205
+ message: `"check.${key}" must be ${expected}.`
206
+ });
207
+ if (!(typeof tsconfig === "string" || strings(tsconfig) && tsconfig.length > 0)) {
208
+ errors.push(invalid("tsconfig", "a tsconfig path, or a list of them"));
209
+ }
210
+ if (baseline !== void 0 && typeof baseline !== "string") {
211
+ errors.push(invalid("baseline", "a path"));
212
+ }
213
+ if (ignore !== void 0 && !strings(ignore)) errors.push(invalid("ignore", "a list of globs"));
214
+ return errors;
215
+ }
187
216
  function checkBindings(bindings) {
188
217
  if (bindings === void 0) return [];
189
218
  if (typeof bindings !== "object" || bindings === null || Array.isArray(bindings)) {
@@ -453,6 +482,13 @@ function checkClients(clients, checkPolicyKey) {
453
482
  return errors;
454
483
  });
455
484
  }
485
+ function resolveCheck(base2, check2) {
486
+ return {
487
+ tsconfig: [check2.tsconfig].flat().map((path) => (0, import_node_path.resolve)(base2, path)),
488
+ ...check2.baseline ? { baseline: (0, import_node_path.resolve)(base2, check2.baseline) } : {},
489
+ ignore: check2.ignore ?? []
490
+ };
491
+ }
456
492
 
457
493
  // src/validate.ts
458
494
  var import_node_path4 = require("node:path");