@opensip-cli/external-tool-adapter 0.1.15

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 (172) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +33 -0
  4. package/dist/__tests__/acceptance-harness.test.d.ts +2 -0
  5. package/dist/__tests__/acceptance-harness.test.d.ts.map +1 -0
  6. package/dist/__tests__/acceptance-harness.test.js +106 -0
  7. package/dist/__tests__/acceptance-harness.test.js.map +1 -0
  8. package/dist/__tests__/artifact-path.test.d.ts +2 -0
  9. package/dist/__tests__/artifact-path.test.d.ts.map +1 -0
  10. package/dist/__tests__/artifact-path.test.js +19 -0
  11. package/dist/__tests__/artifact-path.test.js.map +1 -0
  12. package/dist/__tests__/binary-resolver.test.d.ts +2 -0
  13. package/dist/__tests__/binary-resolver.test.d.ts.map +1 -0
  14. package/dist/__tests__/binary-resolver.test.js +64 -0
  15. package/dist/__tests__/binary-resolver.test.js.map +1 -0
  16. package/dist/__tests__/define-external-tool-adapter.test.d.ts +2 -0
  17. package/dist/__tests__/define-external-tool-adapter.test.d.ts.map +1 -0
  18. package/dist/__tests__/define-external-tool-adapter.test.js +165 -0
  19. package/dist/__tests__/define-external-tool-adapter.test.js.map +1 -0
  20. package/dist/__tests__/doctor-command.test.d.ts +2 -0
  21. package/dist/__tests__/doctor-command.test.d.ts.map +1 -0
  22. package/dist/__tests__/doctor-command.test.js +124 -0
  23. package/dist/__tests__/doctor-command.test.js.map +1 -0
  24. package/dist/__tests__/exit-model.test.d.ts +2 -0
  25. package/dist/__tests__/exit-model.test.d.ts.map +1 -0
  26. package/dist/__tests__/exit-model.test.js +30 -0
  27. package/dist/__tests__/exit-model.test.js.map +1 -0
  28. package/dist/__tests__/fingerprint.test.d.ts +2 -0
  29. package/dist/__tests__/fingerprint.test.d.ts.map +1 -0
  30. package/dist/__tests__/fingerprint.test.js +39 -0
  31. package/dist/__tests__/fingerprint.test.js.map +1 -0
  32. package/dist/__tests__/gate-render.test.d.ts +2 -0
  33. package/dist/__tests__/gate-render.test.d.ts.map +1 -0
  34. package/dist/__tests__/gate-render.test.js +82 -0
  35. package/dist/__tests__/gate-render.test.js.map +1 -0
  36. package/dist/__tests__/ingest-json.test.d.ts +2 -0
  37. package/dist/__tests__/ingest-json.test.d.ts.map +1 -0
  38. package/dist/__tests__/ingest-json.test.js +53 -0
  39. package/dist/__tests__/ingest-json.test.js.map +1 -0
  40. package/dist/__tests__/ingest-sarif.test.d.ts +2 -0
  41. package/dist/__tests__/ingest-sarif.test.d.ts.map +1 -0
  42. package/dist/__tests__/ingest-sarif.test.js +283 -0
  43. package/dist/__tests__/ingest-sarif.test.js.map +1 -0
  44. package/dist/__tests__/manifest-commands.test.d.ts +2 -0
  45. package/dist/__tests__/manifest-commands.test.d.ts.map +1 -0
  46. package/dist/__tests__/manifest-commands.test.js +67 -0
  47. package/dist/__tests__/manifest-commands.test.js.map +1 -0
  48. package/dist/__tests__/provenance.test.d.ts +2 -0
  49. package/dist/__tests__/provenance.test.d.ts.map +1 -0
  50. package/dist/__tests__/provenance.test.js +48 -0
  51. package/dist/__tests__/provenance.test.js.map +1 -0
  52. package/dist/__tests__/redact.test.d.ts +2 -0
  53. package/dist/__tests__/redact.test.d.ts.map +1 -0
  54. package/dist/__tests__/redact.test.js +37 -0
  55. package/dist/__tests__/redact.test.js.map +1 -0
  56. package/dist/__tests__/run-loop-artifact.test.d.ts +21 -0
  57. package/dist/__tests__/run-loop-artifact.test.d.ts.map +1 -0
  58. package/dist/__tests__/run-loop-artifact.test.js +186 -0
  59. package/dist/__tests__/run-loop-artifact.test.js.map +1 -0
  60. package/dist/__tests__/run-loop-exit.test.d.ts +21 -0
  61. package/dist/__tests__/run-loop-exit.test.d.ts.map +1 -0
  62. package/dist/__tests__/run-loop-exit.test.js +123 -0
  63. package/dist/__tests__/run-loop-exit.test.js.map +1 -0
  64. package/dist/__tests__/run-loop-gate.test.d.ts +10 -0
  65. package/dist/__tests__/run-loop-gate.test.d.ts.map +1 -0
  66. package/dist/__tests__/run-loop-gate.test.js +159 -0
  67. package/dist/__tests__/run-loop-gate.test.js.map +1 -0
  68. package/dist/__tests__/session-payload.test.d.ts +12 -0
  69. package/dist/__tests__/session-payload.test.d.ts.map +1 -0
  70. package/dist/__tests__/session-payload.test.js +131 -0
  71. package/dist/__tests__/session-payload.test.js.map +1 -0
  72. package/dist/__tests__/severity-map.test.d.ts +2 -0
  73. package/dist/__tests__/severity-map.test.d.ts.map +1 -0
  74. package/dist/__tests__/severity-map.test.js +57 -0
  75. package/dist/__tests__/severity-map.test.js.map +1 -0
  76. package/dist/acceptance-harness.d.ts +48 -0
  77. package/dist/acceptance-harness.d.ts.map +1 -0
  78. package/dist/acceptance-harness.js +78 -0
  79. package/dist/acceptance-harness.js.map +1 -0
  80. package/dist/adapter-config.d.ts +58 -0
  81. package/dist/adapter-config.d.ts.map +1 -0
  82. package/dist/adapter-config.js +73 -0
  83. package/dist/adapter-config.js.map +1 -0
  84. package/dist/adapter-manifest.d.ts +57 -0
  85. package/dist/adapter-manifest.d.ts.map +1 -0
  86. package/dist/adapter-manifest.js +68 -0
  87. package/dist/adapter-manifest.js.map +1 -0
  88. package/dist/artifact-path.d.ts +26 -0
  89. package/dist/artifact-path.d.ts.map +1 -0
  90. package/dist/artifact-path.js +22 -0
  91. package/dist/artifact-path.js.map +1 -0
  92. package/dist/binary-resolver.d.ts +51 -0
  93. package/dist/binary-resolver.d.ts.map +1 -0
  94. package/dist/binary-resolver.js +66 -0
  95. package/dist/binary-resolver.js.map +1 -0
  96. package/dist/define-external-tool-adapter.d.ts +25 -0
  97. package/dist/define-external-tool-adapter.d.ts.map +1 -0
  98. package/dist/define-external-tool-adapter.js +149 -0
  99. package/dist/define-external-tool-adapter.js.map +1 -0
  100. package/dist/doctor-command.d.ts +81 -0
  101. package/dist/doctor-command.d.ts.map +1 -0
  102. package/dist/doctor-command.js +160 -0
  103. package/dist/doctor-command.js.map +1 -0
  104. package/dist/exit-model.d.ts +33 -0
  105. package/dist/exit-model.d.ts.map +1 -0
  106. package/dist/exit-model.js +35 -0
  107. package/dist/exit-model.js.map +1 -0
  108. package/dist/fingerprint.d.ts +26 -0
  109. package/dist/fingerprint.d.ts.map +1 -0
  110. package/dist/fingerprint.js +32 -0
  111. package/dist/fingerprint.js.map +1 -0
  112. package/dist/gate-render.d.ts +18 -0
  113. package/dist/gate-render.d.ts.map +1 -0
  114. package/dist/gate-render.js +25 -0
  115. package/dist/gate-render.js.map +1 -0
  116. package/dist/index.d.ts +39 -0
  117. package/dist/index.d.ts.map +1 -0
  118. package/dist/index.js +35 -0
  119. package/dist/index.js.map +1 -0
  120. package/dist/ingest-json.d.ts +32 -0
  121. package/dist/ingest-json.d.ts.map +1 -0
  122. package/dist/ingest-json.js +66 -0
  123. package/dist/ingest-json.js.map +1 -0
  124. package/dist/ingest-sarif.d.ts +113 -0
  125. package/dist/ingest-sarif.d.ts.map +1 -0
  126. package/dist/ingest-sarif.js +158 -0
  127. package/dist/ingest-sarif.js.map +1 -0
  128. package/dist/manifest-commands.d.ts +23 -0
  129. package/dist/manifest-commands.d.ts.map +1 -0
  130. package/dist/manifest-commands.js +47 -0
  131. package/dist/manifest-commands.js.map +1 -0
  132. package/dist/process-exec.d.ts +51 -0
  133. package/dist/process-exec.d.ts.map +1 -0
  134. package/dist/process-exec.js +99 -0
  135. package/dist/process-exec.js.map +1 -0
  136. package/dist/provenance.d.ts +19 -0
  137. package/dist/provenance.d.ts.map +1 -0
  138. package/dist/provenance.js +31 -0
  139. package/dist/provenance.js.map +1 -0
  140. package/dist/redact.d.ts +24 -0
  141. package/dist/redact.d.ts.map +1 -0
  142. package/dist/redact.js +38 -0
  143. package/dist/redact.js.map +1 -0
  144. package/dist/run-context.d.ts +24 -0
  145. package/dist/run-context.d.ts.map +1 -0
  146. package/dist/run-context.js +36 -0
  147. package/dist/run-context.js.map +1 -0
  148. package/dist/run-loop.d.ts +64 -0
  149. package/dist/run-loop.d.ts.map +1 -0
  150. package/dist/run-loop.js +320 -0
  151. package/dist/run-loop.js.map +1 -0
  152. package/dist/scan-emit.d.ts +81 -0
  153. package/dist/scan-emit.d.ts.map +1 -0
  154. package/dist/scan-emit.js +125 -0
  155. package/dist/scan-emit.js.map +1 -0
  156. package/dist/session-payload.d.ts +81 -0
  157. package/dist/session-payload.d.ts.map +1 -0
  158. package/dist/session-payload.js +86 -0
  159. package/dist/session-payload.js.map +1 -0
  160. package/dist/severity-map.d.ts +43 -0
  161. package/dist/severity-map.d.ts.map +1 -0
  162. package/dist/severity-map.js +84 -0
  163. package/dist/severity-map.js.map +1 -0
  164. package/dist/types.d.ts +228 -0
  165. package/dist/types.d.ts.map +1 -0
  166. package/dist/types.js +15 -0
  167. package/dist/types.js.map +1 -0
  168. package/dist/version-command.d.ts +36 -0
  169. package/dist/version-command.d.ts.map +1 -0
  170. package/dist/version-command.js +74 -0
  171. package/dist/version-command.js.map +1 -0
  172. package/package.json +52 -0
@@ -0,0 +1,165 @@
1
+ import { createSignal, ValidationError } from '@opensip-cli/core';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { deriveAdapterConfigManifest, deriveAdapterManifestRequires } from '../adapter-manifest.js';
4
+ import { defineExternalToolAdapter } from '../define-external-tool-adapter.js';
5
+ import { messageHashFingerprintStrategy } from '../fingerprint.js';
6
+ const baseSpec = {
7
+ identity: { name: 'examplescan', aliases: ['ex'] },
8
+ metadata: {
9
+ id: 'c0ffee00-1234-4abc-8def-0123456789ab',
10
+ description: 'Example scanner',
11
+ version: '1.2.3',
12
+ adapterPackage: '@opensip-cli/tool-example',
13
+ },
14
+ binary: { command: 'examplescan', versionArgs: ['version'], minVersion: '1.0.0' },
15
+ network: 'local-only',
16
+ commands: [
17
+ {
18
+ name: 'scan',
19
+ args: (ctx) => ['scan', ctx.projectRoot],
20
+ output: { kind: 'sarif', path: 'example.sarif' },
21
+ },
22
+ ],
23
+ };
24
+ describe('defineExternalToolAdapter', () => {
25
+ it('returns an ordinary Tool with identity + metadata derived', () => {
26
+ const tool = defineExternalToolAdapter(baseSpec);
27
+ expect(tool.identity.name).toBe('examplescan');
28
+ expect(tool.metadata).toMatchObject({
29
+ id: 'c0ffee00-1234-4abc-8def-0123456789ab',
30
+ name: 'examplescan',
31
+ version: '1.2.3',
32
+ description: 'Example scanner',
33
+ });
34
+ });
35
+ it('mounts the scan primary + nested doctor + version (3 specs)', () => {
36
+ const tool = defineExternalToolAdapter(baseSpec);
37
+ const specs = tool.commandSpecs ?? [];
38
+ expect(specs.map((s) => s.name)).toEqual(['examplescan', 'doctor', 'version']);
39
+ const [primary, doctor, version] = specs;
40
+ expect(primary).toMatchObject({
41
+ name: 'examplescan',
42
+ scope: 'project',
43
+ output: 'raw-stream',
44
+ rawStreamReason: 'runtime-render-dispatch',
45
+ });
46
+ expect(primary.aliases).toEqual(['ex']);
47
+ expect(doctor).toMatchObject({
48
+ name: 'doctor',
49
+ parent: 'examplescan',
50
+ scope: 'none',
51
+ output: 'raw-stream',
52
+ rawStreamReason: 'diagnostic-gate',
53
+ });
54
+ expect(version).toMatchObject({
55
+ name: 'version',
56
+ parent: 'examplescan',
57
+ scope: 'none',
58
+ output: 'raw-stream',
59
+ rawStreamReason: 'diagnostic-gate',
60
+ });
61
+ });
62
+ it('mounts additional scanner commands as nested verbs', () => {
63
+ const tool = defineExternalToolAdapter({
64
+ ...baseSpec,
65
+ commands: [
66
+ baseSpec.commands[0],
67
+ { name: 'config', args: () => ['config'], output: { kind: 'sarif' } },
68
+ ],
69
+ });
70
+ expect((tool.commandSpecs ?? []).map((s) => s.name)).toEqual([
71
+ 'examplescan',
72
+ 'config',
73
+ 'doctor',
74
+ 'version',
75
+ ]);
76
+ });
77
+ it('declares the message-hash fingerprint strategy by default (worker-side stamping)', () => {
78
+ const tool = defineExternalToolAdapter(baseSpec);
79
+ expect(tool.extensionPoints?.fingerprintStrategy).toBe(messageHashFingerprintStrategy);
80
+ });
81
+ it('forwards an optional config contribution', () => {
82
+ const tool = defineExternalToolAdapter({ ...baseSpec, config: { schema: { marker: true } } });
83
+ expect(tool.extensionPoints?.config).toMatchObject({
84
+ namespace: 'examplescan',
85
+ schema: { marker: true },
86
+ });
87
+ });
88
+ // A4 / R6 (ADR-0090 §4.3): an adapter that declares NO `config` must still CLAIM
89
+ // its namespace by default — otherwise `scope.toolConfig[<tool>]` is always
90
+ // undefined (binary pin dead, verdict keys non-configurable, and an operator's
91
+ // `<tool>:` block bricks the project via the ADR-0043 unclaimed-namespace gate).
92
+ describe('default config namespace claim (A4 / R6)', () => {
93
+ it('claims the namespace on the runtime when config is omitted', () => {
94
+ const tool = defineExternalToolAdapter(baseSpec);
95
+ const config = tool.extensionPoints?.config;
96
+ expect(config?.namespace).toBe('examplescan');
97
+ // The runtime schema is the worker deep-pass Zod (exposes safeParse).
98
+ expect(typeof (config?.schema).safeParse).toBe('function');
99
+ });
100
+ it('the runtime schema accepts the binary pin AND the reserved verdict keys', () => {
101
+ const tool = defineExternalToolAdapter(baseSpec);
102
+ const schema = tool.extensionPoints?.config?.schema;
103
+ expect(schema.safeParse({
104
+ binaries: { examplescan: { path: '/opt/examplescan' } },
105
+ failOnWarnings: 2,
106
+ failOnDegraded: false,
107
+ }).success).toBe(true);
108
+ // A typo inside the block is rejected by the deep pass (strict).
109
+ expect(schema.safeParse({ binares: {} }).success).toBe(false);
110
+ });
111
+ it('emits a coarse static config descriptor (the installed-path namespace claim)', () => {
112
+ const tool = defineExternalToolAdapter(baseSpec);
113
+ const descriptor = deriveAdapterConfigManifest(tool);
114
+ expect(descriptor?.namespace).toBe('examplescan');
115
+ expect(descriptor?.schema.properties?.binaries).toEqual({ type: 'object' });
116
+ });
117
+ it('emits NO static descriptor for a custom config (validation defers to the worker)', () => {
118
+ const tool = defineExternalToolAdapter({ ...baseSpec, config: { schema: { marker: true } } });
119
+ expect(deriveAdapterConfigManifest(tool)).toBeUndefined();
120
+ });
121
+ });
122
+ // A13 (ADR-0092 §4.8): `requires` is DERIVED from the network posture — the
123
+ // documented forward-map, not a hand-typed list. `network` rides only on a
124
+ // non-local-only posture, so a future flip to `networked` is a visible drift.
125
+ describe('network posture → requires derivation (A13)', () => {
126
+ it('a local-only adapter derives [subprocess, filesystem] (no network)', () => {
127
+ const requires = deriveAdapterManifestRequires(defineExternalToolAdapter(baseSpec));
128
+ expect(requires.map((r) => r.resource)).toEqual(['subprocess', 'filesystem']);
129
+ });
130
+ it('a networked adapter derives an added network requirement', () => {
131
+ const tool = defineExternalToolAdapter({ ...baseSpec, network: 'networked' });
132
+ const requires = deriveAdapterManifestRequires(tool);
133
+ expect(requires.map((r) => r.resource)).toEqual(['subprocess', 'filesystem', 'network']);
134
+ expect(requires.find((r) => r.resource === 'network')?.reason).toContain('networked');
135
+ });
136
+ it('an auth-required adapter also derives a network requirement', () => {
137
+ const tool = defineExternalToolAdapter({ ...baseSpec, network: 'auth-required' });
138
+ expect(deriveAdapterManifestRequires(tool).some((r) => r.resource === 'network')).toBe(true);
139
+ });
140
+ });
141
+ it('rejects a non-SARIF command with no parse', () => {
142
+ expect(() => defineExternalToolAdapter({
143
+ ...baseSpec,
144
+ commands: [{ name: 'scan', args: () => [], output: { kind: 'json', path: 'x.json' } }],
145
+ })).toThrow(ValidationError);
146
+ });
147
+ it('accepts a JSON command WITH a parse', () => {
148
+ const tool = defineExternalToolAdapter({
149
+ ...baseSpec,
150
+ commands: [
151
+ {
152
+ name: 'scan',
153
+ args: () => [],
154
+ output: { kind: 'json', path: 'x.json' },
155
+ parse: () => [createSignal({ source: 'x', severity: 'low', ruleId: 'r', message: 'm' })],
156
+ },
157
+ ],
158
+ });
159
+ expect(tool.commandSpecs).toBeDefined();
160
+ });
161
+ it('rejects an empty command list', () => {
162
+ expect(() => defineExternalToolAdapter({ ...baseSpec, commands: [] })).toThrow(ValidationError);
163
+ });
164
+ });
165
+ //# sourceMappingURL=define-external-tool-adapter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-external-tool-adapter.test.js","sourceRoot":"","sources":["../../src/__tests__/define-external-tool-adapter.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=doctor-command.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-command.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/doctor-command.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,124 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { defaultAdapterConfigSchema } from '../adapter-config.js';
3
+ import { compareVersion, doctorReportLines, probeAdapter } from '../doctor-command.js';
4
+ import { probeVersionReport } from '../version-command.js';
5
+ /**
6
+ * Build the `gitleaks` namespace config block the way PRODUCTION does — by parsing
7
+ * it through the adapter's own (default) namespace schema. This is the same Zod the
8
+ * worker deep pass runs and the same shape the composer projects onto
9
+ * `scope.toolConfig.gitleaks`, so a test that feeds the parsed result to
10
+ * `probeAdapter` exercises a genuinely reachable config — not a hand-built shape the
11
+ * resolver could never deliver (the A4 "production-unreachable" hole).
12
+ */
13
+ function producibleConfig(raw) {
14
+ const parsed = defaultAdapterConfigSchema().safeParse(raw);
15
+ if (!parsed.success)
16
+ throw new Error('config block is not producible by the adapter schema');
17
+ return parsed.data;
18
+ }
19
+ function deps(over = {}) {
20
+ return {
21
+ binaryDeps: { existsSync: () => true, which: () => '/usr/bin/gitleaks' },
22
+ probeVersion: () => '8.18.0',
23
+ env: {},
24
+ ...over,
25
+ };
26
+ }
27
+ describe('compareVersion', () => {
28
+ it('classifies ok / too-old / unknown / not-applicable', () => {
29
+ expect(compareVersion('8.18.0', '8.0.0')).toBe('ok');
30
+ expect(compareVersion('8.18.0', '8.18.0')).toBe('ok');
31
+ expect(compareVersion('7.9.9', '8.0.0')).toBe('too-old');
32
+ expect(compareVersion(undefined, '8.0.0')).toBe('unknown');
33
+ expect(compareVersion('not-a-version!', '8.0.0')).toBe('unknown');
34
+ expect(compareVersion('8.0.0', undefined)).toBe('not-applicable');
35
+ });
36
+ });
37
+ describe('probeAdapter', () => {
38
+ const binary = {
39
+ command: 'gitleaks',
40
+ versionArgs: ['version'],
41
+ minVersion: '8.0.0',
42
+ installHint: 'brew install gitleaks',
43
+ };
44
+ it('reports ready when the binary is found and recent enough', () => {
45
+ const report = probeAdapter({ tool: 'gitleaks', network: 'local-only', binary, config: {} }, deps());
46
+ expect(report.binary.found).toBe(true);
47
+ expect(report.binary.layer).toBe('path');
48
+ expect(report.version).toMatchObject({ detected: '8.18.0', minVersion: '8.0.0', status: 'ok' });
49
+ expect(report.ready).toBe(true);
50
+ expect(report.installHint).toBeUndefined();
51
+ });
52
+ it('reports not-ready + an install hint when the binary is missing', () => {
53
+ const report = probeAdapter({ tool: 'gitleaks', network: 'local-only', binary, config: {} }, deps({ binaryDeps: { existsSync: () => false, which: () => undefined } }));
54
+ expect(report.binary.found).toBe(false);
55
+ expect(report.ready).toBe(false);
56
+ expect(report.installHint).toBe('brew install gitleaks');
57
+ });
58
+ it('reports not-ready when the version is too old', () => {
59
+ const report = probeAdapter({ tool: 'gitleaks', network: 'local-only', binary, config: {} }, deps({ probeVersion: () => '7.0.0' }));
60
+ expect(report.version.status).toBe('too-old');
61
+ expect(report.ready).toBe(false);
62
+ });
63
+ it('honors a config-file pin (from a producible namespace block)', () => {
64
+ const report = probeAdapter({
65
+ tool: 'gitleaks',
66
+ network: 'local-only',
67
+ binary,
68
+ // The pin is validated through the adapter's own schema first, so this is
69
+ // exactly the shape the composer projects onto scope.toolConfig.gitleaks —
70
+ // not a production-unreachable hand-built object (A4).
71
+ config: producibleConfig({ binaries: { gitleaks: { path: '/opt/gitleaks' } } }),
72
+ }, deps());
73
+ expect(report.binary.path).toBe('/opt/gitleaks');
74
+ expect(report.binary.layer).toBe('config');
75
+ });
76
+ it('surfaces credential presence (presence only) for an auth-required posture', () => {
77
+ const present = probeAdapter({
78
+ tool: 'snyk',
79
+ network: 'auth-required',
80
+ binary: { command: 'snyk', versionArgs: ['--version'] },
81
+ config: {},
82
+ }, deps({ env: { OPENSIP_SNYK_TOKEN: 'secret-value' } }));
83
+ expect(present.credentialEnv).toEqual({ name: 'OPENSIP_SNYK_TOKEN', present: true });
84
+ expect(present.ready).toBe(true);
85
+ const missing = probeAdapter({
86
+ tool: 'snyk',
87
+ network: 'auth-required',
88
+ binary: { command: 'snyk', versionArgs: ['--version'] },
89
+ config: {},
90
+ }, deps());
91
+ expect(missing.credentialEnv?.present).toBe(false);
92
+ expect(missing.ready).toBe(false);
93
+ });
94
+ });
95
+ describe('doctorReportLines', () => {
96
+ it('renders a found+ready report', () => {
97
+ const lines = doctorReportLines(probeAdapter({
98
+ tool: 'gitleaks',
99
+ network: 'local-only',
100
+ binary: { command: 'gitleaks', versionArgs: ['version'], minVersion: '8.0.0' },
101
+ config: {},
102
+ }, deps()));
103
+ expect(lines.join('\n')).toContain('ready: yes');
104
+ expect(lines.join('\n')).toContain('network: local-only');
105
+ });
106
+ });
107
+ describe('probeVersionReport', () => {
108
+ it('returns the resolved path + version', () => {
109
+ const report = probeVersionReport({ tool: 'gitleaks', binary: { command: 'gitleaks', versionArgs: ['version'] }, config: {} }, deps());
110
+ expect(report).toMatchObject({
111
+ tool: 'gitleaks',
112
+ found: true,
113
+ path: '/usr/bin/gitleaks',
114
+ version: '8.18.0',
115
+ layer: 'path',
116
+ });
117
+ });
118
+ it('reports not-found cleanly', () => {
119
+ const report = probeVersionReport({ tool: 'gitleaks', binary: { command: 'gitleaks', versionArgs: ['version'] }, config: {} }, deps({ binaryDeps: { existsSync: () => false, which: () => undefined } }));
120
+ expect(report).toMatchObject({ found: false, command: 'gitleaks' });
121
+ expect(report.version).toBeUndefined();
122
+ });
123
+ });
124
+ //# sourceMappingURL=doctor-command.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-command.test.js","sourceRoot":"","sources":["../../src/__tests__/doctor-command.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=exit-model.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exit-model.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/exit-model.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { DEFAULT_EXIT_MODEL, interpretExit } from '../exit-model.js';
3
+ const GITLEAKS = { ok: [0], findings: [1], errorFrom: 2 };
4
+ const TRIVY = { ok: [0], findings: [], errorFrom: 1 };
5
+ describe('interpretExit', () => {
6
+ it('classifies ok / findings / fault for the default model', () => {
7
+ expect(interpretExit(0, DEFAULT_EXIT_MODEL)).toBe('ok');
8
+ expect(interpretExit(1, DEFAULT_EXIT_MODEL)).toBe('findings');
9
+ expect(interpretExit(2, DEFAULT_EXIT_MODEL)).toBe('fault');
10
+ expect(interpretExit(127, DEFAULT_EXIT_MODEL)).toBe('fault');
11
+ });
12
+ it('gitleaks disambiguation: exit 1 + invalid artifact ⇒ fault, valid ⇒ findings', () => {
13
+ expect(interpretExit(1, GITLEAKS, { artifactValid: true })).toBe('findings');
14
+ expect(interpretExit(1, GITLEAKS, { artifactValid: false })).toBe('fault');
15
+ // undefined artifactValid is treated as valid (stdout scanners).
16
+ expect(interpretExit(1, GITLEAKS)).toBe('findings');
17
+ });
18
+ it('trivy model: 0 is ok, any nonzero is a fault (no exit-code findings)', () => {
19
+ expect(interpretExit(0, TRIVY)).toBe('ok');
20
+ expect(interpretExit(1, TRIVY)).toBe('fault');
21
+ expect(interpretExit(5, TRIVY)).toBe('fault');
22
+ });
23
+ it('prefers ok when a code appears in both ok and findings', () => {
24
+ expect(interpretExit(0, { ok: [0], findings: [0] })).toBe('ok');
25
+ });
26
+ it('an unmodeled nonzero with no errorFrom is still a fault', () => {
27
+ expect(interpretExit(3, { ok: [0], findings: [1] })).toBe('fault');
28
+ });
29
+ });
30
+ //# sourceMappingURL=exit-model.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exit-model.test.js","sourceRoot":"","sources":["../../src/__tests__/exit-model.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAIrE,MAAM,QAAQ,GAAqB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;AAC5E,MAAM,KAAK,GAAqB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;AAExE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9D,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3D,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8EAA8E,EAAE,GAAG,EAAE;QACtF,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7E,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3E,iEAAiE;QACjE,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAC9E,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fingerprint.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fingerprint.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createSignal, defaultFingerprintStrategy } from '@opensip-cli/core';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { messageHashFingerprintStrategy, resolveFingerprintStrategy } from '../fingerprint.js';
5
+ describe('messageHashFingerprintStrategy', () => {
6
+ it('is sha256(filePath\\nruleId\\nmessage) — line-shift tolerant', () => {
7
+ const signal = createSignal({
8
+ source: 'osv-scanner',
9
+ severity: 'high',
10
+ ruleId: 'GHSA-1',
11
+ message: 'Prototype pollution',
12
+ code: { file: 'package-lock.json', line: 42 },
13
+ });
14
+ const expected = createHash('sha256')
15
+ .update(`package-lock.json\nGHSA-1\nPrototype pollution`)
16
+ .digest('hex');
17
+ expect(messageHashFingerprintStrategy.fingerprint(signal)).toBe(expected);
18
+ });
19
+ it('ignores line/column so a shift does not re-key the baseline', () => {
20
+ const base = {
21
+ source: 'g',
22
+ severity: 'high',
23
+ ruleId: 'R',
24
+ message: 'm',
25
+ code: { file: 'a.txt' },
26
+ };
27
+ const at10 = messageHashFingerprintStrategy.fingerprint(createSignal({ ...base, code: { file: 'a.txt', line: 10 } }));
28
+ const at99 = messageHashFingerprintStrategy.fingerprint(createSignal({ ...base, code: { file: 'a.txt', line: 99 } }));
29
+ expect(at10).toBe(at99);
30
+ });
31
+ });
32
+ describe('resolveFingerprintStrategy', () => {
33
+ it('defaults to message-hash and maps rule-location to the host default', () => {
34
+ expect(resolveFingerprintStrategy(undefined)).toBe(messageHashFingerprintStrategy);
35
+ expect(resolveFingerprintStrategy('message-hash')).toBe(messageHashFingerprintStrategy);
36
+ expect(resolveFingerprintStrategy('rule-location')).toBe(defaultFingerprintStrategy);
37
+ });
38
+ });
39
+ //# sourceMappingURL=fingerprint.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.test.js","sourceRoot":"","sources":["../../src/__tests__/fingerprint.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,8BAA8B,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAE/F,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,MAAM,GAAG,YAAY,CAAC;YAC1B,MAAM,EAAE,aAAa;YACrB,QAAQ,EAAE,MAAM;YAChB,MAAM,EAAE,QAAQ;YAChB,OAAO,EAAE,qBAAqB;YAC9B,IAAI,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,EAAE,EAAE;SAC9C,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC;aAClC,MAAM,CAAC,gDAAgD,CAAC;aACxD,MAAM,CAAC,KAAK,CAAC,CAAC;QACjB,MAAM,CAAC,8BAA8B,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,IAAI,GAAG;YACX,MAAM,EAAE,GAAG;YACX,QAAQ,EAAE,MAAe;YACzB,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,GAAG;YACZ,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;SACxB,CAAC;QACF,MAAM,IAAI,GAAG,8BAA8B,CAAC,WAAW,CACrD,YAAY,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,CAC7D,CAAC;QACF,MAAM,IAAI,GAAG,8BAA8B,CAAC,WAAW,CACrD,YAAY,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,CAC7D,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,CAAC,0BAA0B,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QACnF,MAAM,CAAC,0BAA0B,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QACxF,MAAM,CAAC,0BAA0B,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gate-render.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-render.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/gate-render.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,82 @@
1
+ import { createSignal } from '@opensip-cli/core';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { renderGateCompareLines, renderGateSaveLines } from '../gate-render.js';
4
+ function sig(over = {}) {
5
+ return createSignal({
6
+ source: 'examplescan',
7
+ severity: 'high',
8
+ ruleId: 'secret',
9
+ message: 'A secret',
10
+ code: { file: 'src/a.ts', line: 3 },
11
+ ...over,
12
+ });
13
+ }
14
+ function result(over = {}) {
15
+ return { added: [], resolved: [], unchanged: [], degraded: false, ...over };
16
+ }
17
+ describe('renderGateSaveLines', () => {
18
+ it('reports the tool + finding count', () => {
19
+ expect(renderGateSaveLines('examplescan', 3)).toEqual([
20
+ 'examplescan: baseline saved (project SQLite store)',
21
+ ' 3 finding(s) recorded',
22
+ ]);
23
+ });
24
+ });
25
+ describe('renderGateCompareLines', () => {
26
+ it('renders STABLE when there is no change', () => {
27
+ const lines = renderGateCompareLines('examplescan', result({ unchanged: [sig()] }));
28
+ expect(lines[0]).toBe('examplescan gate compare');
29
+ expect(lines.at(-1)).toBe('✓ STABLE — no change');
30
+ expect(lines.join('\n')).toContain('Unchanged (1):');
31
+ });
32
+ it('renders DEGRADED with the net-new finding + message when added is non-empty', () => {
33
+ const added = sig({
34
+ ruleId: 'aws-key',
35
+ message: 'AWS Access Key',
36
+ code: { file: 'p.env', line: 9 },
37
+ });
38
+ const lines = renderGateCompareLines('examplescan', result({ added: [added], degraded: true }));
39
+ const text = lines.join('\n');
40
+ expect(text).toContain('Added (1):');
41
+ expect(text).toContain('aws-key');
42
+ expect(text).toContain('p.env:9');
43
+ expect(text).toContain('AWS Access Key');
44
+ expect(lines.at(-1)).toBe('✗ DEGRADED — 1 new finding');
45
+ });
46
+ it('pluralizes the DEGRADED footer for multiple new findings', () => {
47
+ const lines = renderGateCompareLines('examplescan', result({ added: [sig({ ruleId: 'a' }), sig({ ruleId: 'b' })], degraded: true }));
48
+ expect(lines.at(-1)).toBe('✗ DEGRADED — 2 new findings');
49
+ });
50
+ it('renders IMPROVED when findings resolved and none added', () => {
51
+ const lines = renderGateCompareLines('examplescan', result({ resolved: [sig(), sig({ ruleId: 'b' })] }));
52
+ expect(lines.join('\n')).toContain('Resolved (2):');
53
+ expect(lines.at(-1)).toBe('✓ IMPROVED — 2 findings resolved, none added');
54
+ });
55
+ it('samples the unchanged bucket to the first five with an overflow note', () => {
56
+ const unchanged = Array.from({ length: 7 }, (_, i) => sig({ ruleId: `r${i}`, code: { file: `f${i}.ts`, line: i } }));
57
+ const text = renderGateCompareLines('examplescan', result({ unchanged })).join('\n');
58
+ expect(text).toContain('Unchanged (7):');
59
+ expect(text).toContain('... and 2 more');
60
+ });
61
+ it('renders a no-location placeholder for a signal with no file', () => {
62
+ const noFile = sig({ code: { file: '' } });
63
+ const text = renderGateCompareLines('examplescan', result({ added: [noFile], degraded: true })).join('\n');
64
+ expect(text).toContain('(no location)');
65
+ });
66
+ it('orders same-rule findings by file then line, and truncates a long message', () => {
67
+ const longMsg = 'x'.repeat(200);
68
+ const added = [
69
+ sig({ ruleId: 'dup', message: longMsg, code: { file: 'b.ts', line: 9 } }),
70
+ sig({ ruleId: 'dup', message: longMsg, code: { file: 'a.ts', line: 2 } }),
71
+ sig({ ruleId: 'dup', message: longMsg, code: { file: 'a.ts', line: 1 } }),
72
+ ];
73
+ const lines = renderGateCompareLines('examplescan', result({ added, degraded: true }));
74
+ const locOrder = lines.filter((l) => l.includes('dup')).map((l) => l.split(/\s+/).at(-1));
75
+ expect(locOrder).toEqual(['a.ts:1', 'a.ts:2', 'b.ts:9']);
76
+ // The 200-char message is truncated with an ellipsis to <= 120 chars.
77
+ const msgLine = lines.find((l) => l.trimStart().startsWith('x'));
78
+ expect(msgLine?.trim().length).toBeLessThanOrEqual(120);
79
+ expect(msgLine).toContain('…');
80
+ });
81
+ });
82
+ //# sourceMappingURL=gate-render.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-render.test.js","sourceRoot":"","sources":["../../src/__tests__/gate-render.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ingest-json.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest-json.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ingest-json.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,53 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { asArray, asObject, getNumber, getString, navigate, safeParseJson, } from '../ingest-json.js';
3
+ describe('safeParseJson', () => {
4
+ it('returns ok for valid JSON', () => {
5
+ expect(safeParseJson('{"a":1}')).toEqual({ ok: true, value: { a: 1 } });
6
+ expect(safeParseJson('[]')).toEqual({ ok: true, value: [] });
7
+ });
8
+ it('returns an error Result for malformed JSON instead of throwing', () => {
9
+ const result = safeParseJson('{not json');
10
+ expect(result.ok).toBe(false);
11
+ if (!result.ok)
12
+ expect(typeof result.error).toBe('string');
13
+ });
14
+ });
15
+ describe('asObject / asArray', () => {
16
+ it('narrows plain objects but not arrays or null', () => {
17
+ expect(asObject({ a: 1 })).toEqual({ a: 1 });
18
+ expect(asObject([])).toBeUndefined();
19
+ expect(asObject(null)).toBeUndefined();
20
+ expect(asObject('x')).toBeUndefined();
21
+ });
22
+ it('narrows arrays only', () => {
23
+ expect(asArray([1, 2])).toEqual([1, 2]);
24
+ expect(asArray({})).toBeUndefined();
25
+ });
26
+ });
27
+ describe('getString / getNumber', () => {
28
+ it('reads typed properties', () => {
29
+ expect(getString({ k: 'v' }, 'k')).toBe('v');
30
+ expect(getString({ k: 1 }, 'k')).toBeUndefined();
31
+ expect(getString(undefined, 'k')).toBeUndefined();
32
+ });
33
+ it('coerces numeric strings for numbers', () => {
34
+ expect(getNumber({ k: 3 }, 'k')).toBe(3);
35
+ expect(getNumber({ k: '7.5' }, 'k')).toBe(7.5);
36
+ expect(getNumber({ k: 'nope' }, 'k')).toBeUndefined();
37
+ expect(getNumber({ k: Number.NaN }, 'k')).toBeUndefined();
38
+ expect(getNumber({}, 'k')).toBeUndefined();
39
+ });
40
+ });
41
+ describe('navigate', () => {
42
+ const doc = { results: [{ packages: [{ id: 'GHSA-1' }] }] };
43
+ it('descends objects and array indices', () => {
44
+ expect(navigate(doc, ['results', '0', 'packages', '0', 'id'])).toBe('GHSA-1');
45
+ });
46
+ it('returns undefined on a missing or wrong-typed step', () => {
47
+ expect(navigate(doc, ['results', '9', 'id'])).toBeUndefined();
48
+ expect(navigate(doc, ['missing'])).toBeUndefined();
49
+ expect(navigate(null, ['x'])).toBeUndefined();
50
+ expect(navigate(doc, ['results', 'notanindex'])).toBeUndefined();
51
+ });
52
+ });
53
+ //# sourceMappingURL=ingest-json.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest-json.test.js","sourceRoot":"","sources":["../../src/__tests__/ingest-json.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ingest-sarif.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest-sarif.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ingest-sarif.test.ts"],"names":[],"mappings":""}